Search code examples
c++templatestemplate-meta-programmingsfinae

Why doesn't the compiler choose the other overload when I remove template?


I'm trying to write C++ code to understand whether some class has a default constructor or not (without C++20 concepts). The following code works just fine.

#include <iostream>

class C {
public:
    C() = delete;
};

template<typename T>
struct HasDefaultConstructor {
    template<typename U>
    static auto test(int) -> decltype(U(), std::true_type{});

    template<typename U>
    static auto test(...) -> std::false_type;

    static constexpr bool value = decltype(test<T>(0))::value;
};

int main() {
    bool a = HasDefaultConstructor<C>::value;
    std::cout << std::boolalpha << a << std::endl;
    return 0;
}

However when I change the test functions to the ones below and use decltype(test(0))::value I get a compilation error.

    static auto test(int) -> decltype(T(), std::true_type{});
    static auto test(...) -> std::false_type;

The error says that C::C() is a deleted function. I'm wondering why doesn't the compiler complain when it's a templated version and why doesn't it choose the other version of test(test(...)) when the version with int fails to compile? Could you please explain how SFINAE works here?


Solution

  • I'm wondering why doesn't the compiler complain when it's a templated version and why doesn't it choose the other version of test(test(...)) when the version with int fails to compile?

    In order to work the SFINAE, the template parameter must be from the SFINAE'ed function template overloads.

    From cppreference.com:

    This rule applies during overload resolution of function templates: When substituting the explicitly specified or deduced type for the template parameter fails, the specialization is discarded from the overload set instead of causing a compile error.

    At your second case, they are not any more function templates rather members using the class template parameter for defining their trailing return types.

    template<typename T>
    struct HasDefaultConstructor
    {
       // following are not function templates!!
       static auto test(int) -> decltype(T(), std::true_type{});
       static auto test(...) -> std::false_type;
       // ....
    };
    

    i.e. You are using the template parameter type T directly from the class template, instead of the template parameter of the functions (i.e. U). This changes the behavior because the type T is resolved during the substitution stage, even if the expression T() is not evaluated.

    The problem is that the failing substitution is happening in the return type of the function templates, which is not part of the immediate context for SFINAE. As a result, the compiler reports an error instead of applying SFINAE and selecting the second overload.