简体   繁体   中英

C++ SFINAE : is_constructible for const char[] vs std::string

I'm trying to disable ctor that has non-std::string constructible type. My 1st attempt was like this :

#include <iostream>

struct A
{
    template <typename U, typename = typename std::enable_if<std::is_constructible<std::string, U>::value>::type>
    A(U&& val)
    {
        std::cout << "A(std::string&& val)" << std::string(std::forward<U>(val)) << std::endl;
    }

    template <typename U, typename = typename std::enable_if<not std::is_constructible<std::string, U>::value>::type>
    A(U&& val)
    {
        std::cout << "A(int&& val)" << val << std::endl;
    }
};

int main()
{
    A a1(1);
    A a2("hello");
    A a3(std::string("hello"));
}

But compilation fails in line

A a1(1);

with the following error message :

error C2535: 'A::A(U &&)': member function already defined or declared ( live example ).

That means, that both conditions for SFINAE succeeded and both ctors are used.

I moved on and tried the following approach :

#include <iostream>

struct A
{
    template <typename U>
    A(U&& val, typename std::enable_if<std::is_constructible<std::string, U>::value>::type* = nullptr)
    {
        std::cout << "A(std::string&& val)" << std::string(std::forward<U>(val)) << std::endl;
    }

    template <typename U>
    A(U&& val, typename std::enable_if<not std::is_constructible<std::string, U>::value>::type* = nullptr)
    {
        std::cout << "A(int&& val)" << val << std::endl;
    }
};

int main()
{
    A a1(1);
    A a2("hello");
    A a3(std::string("hello"));
}

Luckily it compiles and works fine ( live example ).

So far, Im pretty fine with the 2nd solution but I don't really understand why the 1st approach with enabling/disabling ctor using templated parameter doesn't work.

The conditions are not both true, that would be impossible. You can be assured of it by the fact the second approach works, which would not happen if both were still true.

What's important to remember is that default template arguments are not part of the function template's signature. If we somewhat reduce the two c'tors down to their signatures, we'll get this:

template <typename U, typename>
A(U&& val)
{
}

template <typename U, typename>
A(U&& val)
{
}

And the two are identical. So what happens is template argument deduction for U and an attempt to do substitution to see which overload to pick. Even though we can't complete template argument deduction for one of the overloads (the last argument is always unaccounted for in one of them), we still have two templates with identical signatures being found when attempting the deduction process. So the program becomes ill-formed.

The second approach works because the signature itself depends on the enable_if being evaluated. That's why one of the two overloads will always be silently removed, as though it was never there.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM