VS2012 SP1 (+november pack) unknown-type errors (a

2019-08-12 03:26发布

So ms compiler online could not compile this (as wall as my home VS2012 with SP1 (+november pack)) while clang and modern gcc could. Can aany one please tall me what C++11 feature is missing in VS and is there ways around?

#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;
}

Errors:

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条回答
我只想做你的唯一
2楼-- · 2019-08-12 03:44

[This answer has been updated. See the EDIT at the end of the text]

I brought this down to an 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 and Clang 3.2 compile this, while VC11 does not. Indeed it seems VC11 does not apply SFINAE when substitution fails inside the decltype expression, and this is most likely a bug.

In fact, the C++11 Standard specifies (14.8.2/7):

The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions. [...]

Also relevant to SFINAE is 14.8.2/8, which adds:

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [...] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.

So is this a case of substitution failure "in an immediate context"? The same paragraph clarifies what is meant by "immediate context":

Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such side effects are not in the “immediate context” and can result in the program being ill-formed.

In the case of my simplified example, the substitution failure definitely occurs in an immediate context, as it involves no template instantiation or specialization whatsovever. Thus, VC11 definitely contains a bug.

However, it is less obvious whether in your example the substitution happens in an "immediate context", because inside the decltype expression a function template (_b_caller__) instantiation is attempted.

The key observation here is that the instantiation is attempted but never performed, because type deduction fails (again, due to substitution failure for the expression in the decltype clause of the template function whose instantiation is attempted). Thus, the error does not occur in the nested context of a template instantiation.

Hence, this qualifies as a VC11 bug.

P.S.: See this Q&A on SO for a situation where SFINAE does not apply because substitution failure occurs in a nested context.

EDIT:

It turns out my answer was incorrect, but I decided to keep its original text because I believe the reasoning is non-trivial and might be helpful to some. However, I overlooked one important aspect.

As Johannes Schaub correctly pointed out in a comment below, the second definition of f() above is ill-formed and no diagnostic is required. This is dictated by Paragraph 14.6/8 of the C++11 Standard:

[...] If every valid specialization of a variadic template requires an empty template parameter pack, the template definition is ill-formed, no diagnostic required. [...]

Thus, although the program is ill-formed, compilers are not required (even though they are allowed) to issue an error. This means that failure to compile this program is NOT a bug of VC11, but rather a good feature, in that the compiler detects an error it is not required to detect (although it must be said that the error message is quite misleading).

查看更多
我只想做你的唯一
3楼-- · 2019-08-12 03:44

That's complicated. I guess, GCC and CLANG are using SFINAE to disambiguate the two function templates, that at first glance are ambiguos. Lets look at an example: The call c.b(2445) I'm gonna mess up a bit the types and actual arguments, but I hope it's understandable what I mean.

  1. Instantiating the first function template, meaning

    auto b<int>(int args)->decltype(_b_caller__(_first__, std::forward <int> (args))), which in turn instantiates _b_caller<A,int>, which calls _first__->b(int). Since A has no method b, both instantiations fail. This leads to

  2. Instantiating the second function template, meaning

    auto b<int>(int args)->decltype(_b_caller__(_second__, std::forward <int> (args))) and _b_caller<B,int> which works, since B has a method b(double).

It seems that somewehere in that process Visual Studio bails out. My guess is, that SFINAE does not work correctly with trailing return types, but it might as well be the two level deep instantiation that makes it difficult to apply SFINAE correctly in that case.

Edit: this might be related: Why does SFINAE not apply to this?

查看更多
登录 后发表回答