VS2012 SP1(+ 11月包装)未知类型错误(类似C :: a(T&& ...))

时间:2013-02-01 14:25:17

标签: c++ visual-studio c++11 visual-studio-2012

所以ms compiler online could not compile this(与我的家VS2012 SP1(+ 11月包)一样)clang and modern gcc could。请不要高兴我在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'

2 个答案:

答案 0 :(得分: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没有编译。事实上,当decltype表达式中的替换失败时,VC11似乎不适用SFINAE,这很可能是错误

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

  

替换发生在函数类型和模板参数声明中使用的所有类型和表达式中。表达式不仅包括常量表达式,例如出现在数组边界中的常量表达式,还包括非强制模板参数,还包括sizeof内的一般表达式(即非常量表达式),decltype ,以及允许非常量表达式的其他上下文。 [...]

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

  

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

这是一个“在直接背景下”替换失败的情况吗?同一段阐明了“直接背景”的含义:

  

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

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

但是,在你的示例中,是否在“立即上下文”中进行替换是不太明显的,因为在decltype表达式中有一个函数模板(_b_caller__)实例化是尝试

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

因此,这符合 VC11错误

P.S。:有关SFINAE不适用的情况,请参阅this Q&A on SO,因为在嵌套环境中发生替换失败。

修改

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

正如Johannes Schaub在下面的评论中正确指出的那样,上面decltype的第二个定义是不正确的,不需要诊断。这取决于C ++ 11标准的第14.6 / 8段:

  

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

因此,虽然程序格式不正确,但不需要编译器(即使允许编译器)发出错误。这意味着编译此程序的失败不是VC11的错误,而是一个很好的功能,因为编译器检测到不需要检测的错误(尽管必须说错误)消息很容易误导。)

答案 1 :(得分: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(双重)。

  3. 似乎有些人在Visual Studio的这个过程中躲避了。我的猜测是,SFINAE不能正确处理尾随返回类型,但它也可能是两级深度实例化,这使得在这种情况下难以正确应用SFINAE。

    修改:这可能与此有关: Why does SFINAE not apply to this?