Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE in variadic constructor

I want to define a generic strong alias type, i.e. a type

template<typename T, auto ID = 0>
class StrongAlias {
    T value;
};

such that for a type T a StrongAlias<T> can be used in exactly the same way as T, but StrongAlias<T, 0> and StrongAlias<T, 1> are different types that can not be implecitly converted to each other. In order to mimic a T as perfectly as possible, I would like my StrongAlias to have the same constructors as T. This means I would like to do something like the following:

template<typename T, auto ID = 0>
class StrongAlias {
    T value;
public:
    // doesn't work
    template<typename... Args, typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
    StrongAlias(Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
        : value(std::forward<Args>(args)...) {}
};

except that this wouldn't work since the template parameter pack must be the last template parameter, as clang 5.0 would tell me. The other way to use SFINAE that I thought of would be in the return type, but since a constructor doesn't have a return type, this does not seem to work either.

Is there any way to use SFINAE on a variadic template parameter pack in a constructor?

Alternatively, if there isn't one, can I accomplish what I want in another way?

Note that being implicitly constructible from a T isn't enough in my case, as the example of StrongAlias<std::optional<int>> shows: If StrongAlias can only be implictly constructed from a std::optional<int>, it cannot be be constructed from a std::nullopt (of type std::nullopt_t), because that would involve 2 user-defined conversions. I really want to have all constructors of the aliased type.

EDIT: Of course it would be possible to implement this without SFINAE and let the program be invalid if a StrongAlias is constructed from incompatible arguments. However, while this would be an acceptable behaviour in my specific case, it is clearly not optimal as the StrongAlias may be used in a template that queries if the given type is constructible from some arguments (via std::is_constructible). While this would yield a std::false_type for T, it would result in a std::true_type for StrongAlias<T>, which could mean unnecessary compile errors for StrongAlias<T> that wouldn't exist for T.

like image 652
Irgendwhy Avatar asked Mar 19 '18 21:03

Irgendwhy


2 Answers

Just change std::enable_if_t to a non-type template parameter:

template<typename T, auto ID = 0>
class StrongAlias {
    T value;
public:
    template<typename... Args, std::enable_if_t<std::is_constructible_v<T, Args...>, int> = 0>
    StrongAlias(Args&&... args) noexcept(noexcept(T(std::declval<Args>()...)))
        : value(std::forward<Args>(args)...) {}
};
like image 173
llllllllll Avatar answered Oct 09 '22 07:10

llllllllll


The two issues with your snippet that make it not compile are

  • Trying to pass a type std::is_constructible<T, Args...> as the first argument of std::enable_if_t;
  • Trying to pass decltype(...) to a noexcept operator.

(There's also a third problem with the function style cast inside the noexcept, but that only affects semantics, not compilability.)

Neither causes the error message you cite, which concerns a rule that doesn't apply to function templates at all. With these two problems fixed, Wandbox's Clang 5.0 happily accepts it.

like image 39
T.C. Avatar answered Oct 09 '22 09:10

T.C.