1

所以ms 在线编译器无法编译这个(就像我家 VS2012 和 SP1(+ 十一月包)一样),而clang 和现代 gcc 可以。谁能告诉我VS中缺少什么C ++ 11功能,有什么办法吗?

#include <iostream>
#include <utility>
#include <type_traits>

struct A {
    int x;

    void a() {
        std::cout << "an a! " << x << "\n";
    }
};

struct B {
    double x;

    double b(double k) {
        std::cout << "b! " << x << ", " << k << "\n";
        return x - k;
    }

    void b() {
        std::cout << "b! " << x << ", ?\n";
    }
};

struct C {
    A *_first__;
    B *_second__;
     C(A * _first__, B * _second__):_first__(_first__), _second__(_second__) {
    } template < typename K, typename ... T > static auto _a_caller__(K * k, T && ... args)->decltype(k->a(std::forward < T > (args) ...)) {
    return k->a(std::forward < T > (args)...);
    }
    template < typename...T > auto a(T &&...args)->decltype(_a_caller__(_first__, std::forward < T > (args)...)) {
        return _a_caller__(_first__, std::forward < T > (args)...);
    }
    template < typename...T > auto a(T &&...args)->decltype(_a_caller__(_second__, std::forward < T > (args)...)) {
        return _a_caller__(_second__, std::forward < T > (args)...);
    }
    template < typename K, typename...T > static auto _b_caller__(K * k, T && ... args)->decltype(k->b(std::forward < T > (args) ...)) {
        return k->b(std::forward < T > (args)...);
    }
    template < typename...T > auto b(T &&...args)->decltype(_b_caller__(_first__, std::forward < T > (args)...)) {
        return _b_caller__(_first__, std::forward < T > (args)...);
    }
    template < typename...T > auto b(T &&...args)->decltype(_b_caller__(_second__, std::forward < T > (args)...)) {
        return _b_caller__(_second__, std::forward < T > (args)...);
    }
};

int main() {
    A a {12};
    B b {24};

    C c (&a, &b);

    c.a();
    c.b();
    std::cout << c.b(2445) << std::endl;
}

错误:

testvc.cpp
--\testvc.cpp(38) : error C2535: 'unknown-type C::a(T &&...)' : member function already defined or declared
        --\testvc.cpp(33) : see declaration of 'C::a'
--\testvc.cpp(47) : error C2535: 'unknown-type C::b(T &&...)' : member function already defined or declared
        --\testvc.cpp(42) : see declaration of 'C::b'
--\testvc.cpp(56) : error C2893: Failed to specialize function template 'unknown-type C::a(T &&...)'
        With the following template arguments:
        ''
--\testvc.cpp(57) : error C2893: Failed to specialize function template 'unknown-type C::b(T &&...)'
        With the following template arguments:
        ''
--\testvc.cpp(58) : error C2893: Failed to specialize function template 'unknown-type C::b(T &&...)'
        With the following template arguments:
        'int'
4

2 回答 2

3

[此答案已更新。见文末编辑]

我把它归结为SSCCE

#include <iostream>

struct A { A g(int) { return A(); } };
struct B { B g() { return B(); } };

struct C
{
    template<typename... Ts>
    auto f(Ts... ts) -> decltype(A().g(ts...)) 
    { std::cout << "f -> A" << std::endl; return A(); }

    template<typename... Ts>
    auto f(Ts... ts) -> decltype(B().g(ts...)) 
    { std::cout << "f -> B" << std::endl; return B(); }
};

int main()
{
    C c;
    c.f(1);
}

GCC 4.7.2 和 Clang 3.2 编译这个,而 VC11 没有。事实上,当表达式内的替换失败时,VC11 似乎不应用 SFINAE decltype,这很可能是一个错误

事实上,C++11 标准规定 (14.8.2/7):

替换发生在函数类型和模板参数声明中使用的所有类型和表达式中。表达式不仅包括常量表达式,例如出现在数组边界或作为非类型模板参数的常量表达式,还包括在和其他允许非常量表达式的上下文中的通用表达式(即非常量表达式) 。sizeofdecltype[...]

与 SFINAE 相关的还有 14.8.2/8,它增加了:

如果替换导致无效的类型或表达式,则类型推导失败。无效类型或表达式是如果使用替换参数编写的格式错误的类型或表达式。[...] 只有在函数类型及其模板参数类型的直接上下文中的无效类型和表达式才会导致推导失败。

那么这是“在直接上下文中”替换失败的情况吗?同一段阐明了“直接上下文”的含义:

注意:替换类型和表达式的评估可能会导致副作用,例如类模板特化和/或函数模板特化的实例化,隐式定义函数的生成等。此类副作用不在“立即上下文”中” 并且可能导致程序格式错误。

在我的简化示例中,替换失败肯定发生在直接上下文中,因为它不涉及任何模板实例化或专门化。因此,VC11 肯定包含一个错误

但是,在您的示例中,替换是否发生在“立即上下文”中不太明显,因为在表达式内部尝试decltype了函数模板(_b_caller__)实例化。

这里的关键观察是实例化已尝试但从未执行,因为类型推导decltype失败(再次,由于尝试实例化的模板函数的子句中的表达式替换失败)。因此,该错误不会发生在模板实例化的嵌套上下文中。

因此,这被称为VC11 错误

PS:有关SFINAE 不适用的情况,请参阅关于 SO 的此问答,因为替换失败发生在嵌套上下文中。

编辑:

事实证明我的回答是不正确的,但我决定保留其原始文本,因为我相信推理并非微不足道,并且可能对某些人有所帮助。但是,我忽略了一个重要方面。

正如Johannes Schaub在下面的评论中正确指出的那样,f()上面的第二个定义是错误的,不需要诊断。这是由 C++11 标准的第 14.6/8 段规定的:

[...]如果可变参数模板的每个有效特化都需要一个空模板参数包,则模板定义格式错误,不需要诊断。[...]

因此,尽管程序格式错误,但编译器不需要(即使它们被允许)发出错误。这意味着无法编译这个程序不是 VC11 的错误,而是一个很好的特性,因为编译器检测到一个不需要检测的错误(尽管必须说错误消息非常具有误导性)。

于 2013-02-01T15:39:54.430 回答
1

这很复杂。我猜,GCC 和 CLANG 正在使用 SFINAE 来消除这两个函数模板的歧义,乍一看是模棱两可的。让我们看一个例子:调用c.b(2445)我会弄乱类型和实际参数,但我希望我的意思可以理解。

  1. 实例化第一个函数模板,含义

    auto b<int>(int args)->decltype(_b_caller__(_first__, std::forward <int> (args))),它反过来实例化_b_caller<A,int>,它调用_first__->b(int). 因为 A 没有方法 b,所以两个实例化都失败了。这将导致

  2. 实例化第二个函数模板,含义

    auto b<int>(int args)->decltype(_b_caller__(_second__, std::forward <int> (args)))并且_b_caller<B,int>有效,因为 B 有一个方法 b(double)。

似乎 Visual Studio 在这个过程中的某个地方退出了。我的猜测是,SFINAE 不能与尾随返回类型正常工作,但也可能是两级深度实例化使得在这种情况下很难正确应用 SFINAE。

编辑:这可能是相关的: 为什么 SFINAE 不适用于这个?

于 2013-02-01T15:12:18.917 回答