I have the following class:
template <typename T=void>
class Foo{
public:
Foo(){};
template <typename = typename std::enable_if_t<!std::is_void<T>::value, std::nullptr_t>>
Foo(const T&){};
};
int main() {
Foo<void> v;
}
v is created using the first constructor. Therefore, there is no need to create the second constructor for Foo<void>.
Why does it get created anyways?
The problem is that explicitly creating the second constructor with the type void bypasses SFINAE, and tries to make a parameter of const void&. This is obviously not allowed.
How can I prevent the second constructor from being valid if T is void?
CodePudding user response:
Why does it get created anyways?
Because in your template constructor
template <typename = typename std::enable_if_t<!std::is_void<T>::value, std::nullptr_t>>
Foo(const T&){};
the value of the test for std::enable_if (!std::is_void<T>::value) depends from the template type of the class (T).
To SFINAE enable/disable a method of a class (or struct) you have to write a test that depends from a template parameter of the method itself.
A way to circumvent this problem is add a template parameter U for the method and give it T as default type. I mean something as
template <typename U = T,
typename = std::enable_if_t<!std::is_void<U>::value, std::nullptr_t>>
Foo(const U&){} // ..... the test depends from U ---^
// ^--- U also here, to avoid the void reference problem
or, maybe better,
template <typename U = T,
std::enable_if_t<!std::is_void<U>::value, std::nullptr_t> = nullptr>
Foo(const U&){}
CodePudding user response:
An alternative to disabling the constructor is to replace const T& with some unusable type when T is void:
struct unusable {
unusable() = delete;
unusable(const unusable&) = delete;
~unusable() = delete;
};
template <typename T=void>
class Foo{
using cref = std::conditional_t<std::is_void_v<T>, unusable, std::add_lvalue_reference_t<const T>>;
public:
Foo(){}
Foo(cref){}
};
This is useful const T& it is used in multiple places so you don't have to use SFINAE everywhere.
