c++templatesconstructorsfinae

Using SFINAE in constructor, to check if constructor of a member type exists


#include <string>
#include <type_traits>

struct A {
    A(int) {}
};

template<typename... Args>
auto make_A(const Args&... args) -> decltype(A(args...)) {
    return A(args...);
}

struct B {
    template<typename... Args
        //std::enable_if ?
    >
    B(const Args&... args) : a(args...) { } 
    
    A a;
};


int main() {
    A a1 = make_a(123);
    //A a2 = make_a(std::string("123")); // no make_a<std::string>
    
    B b1(123); // ok 
    B b2(std::string("123")); // fails because A(std::string) does not exist,
                              // but should fail already because there is no B(std::string)
}

In thus code A can only be constructed with an int argument.

make_a uses SFINAE, so that make_a<Args...> only gets instantiated when A::A(Args...) exists. It does this using decltype() in the return type, where args is available.

Is it also possible to restrict the templated constructor B::B<Args...> in a similar way? Here the SFINAE-triggering expression can only be in the template arguments.


Solution

  • In C++20 you can do it with a constraint and a requires expression:

    struct B {
        B(const auto&... args) requires(requires { A(args...); }) : a(args...) { }
        A a;
    };
    

    Or if you don't want to use the noexcept specifier, you won't have access to args, and you can use std::declval<const Args&>() instead:

    template<typename... Args,
             decltype(static_cast<void>(A(std::declval<const Args&>()...)), nullptr) = nullptr>
    B(const Args&... args) : a(args...) { }