You can use std::enable_if for SFINAE from the constructor that executes static_assert , depending on whether T member of the value() function, keeping the actual implementation separate.
The first constructor is selected if T has a value() method and is implemented as usual (except that std::enable_if is required for selection):
template <typename T, typename = std::enable_if_t<HasValueMethod<T>::value>> MyClass(const T &t) : m_value(t.value()) {}
So, we need the second constructor, which must be SFINAEd from the function overload, since the first one already knows that T::value exists:
template <typename T, typename = std::enable_if_t<!HasValueMethod<T>::value>> MyClass(const T &, ...) { static_assert(HasValueMethod<T>::value, "T must have a value() method"); }
Pay attention to the variable parameter ... : it is necessary in order to differentiate the prototype of the constructor, therefore it does not collide with the first (they must be different, otherwise ambiguous prototypes lead to a compilation error). You don’t give anything to him, it’s just to make him another prototype.
Note also that the predicate for std::enable_if the same, but negated. When HasValueMethod<T>::value is false, the first constructor is SFINAEd from the function overload, but not the second, which then launches a static assert.
You still need to use HasValueMethod<T>::value in the static assert parameter, so it will depend on T Otherwise, if you set only false , it will always start regardless of whether it is selected.
Here GCC prints when T does not have .value() :
main.cpp: In instantiation of 'MyClass::MyClass(const T&, ...) [with T = A; <template-parameter-1-2> = void]': main.cpp:35:18: required from here main.cpp:21:9: error: static assertion failed: T must have a value() method static_assert(HasValueMethod<T>::value, "T must have a value() method"); ^~~~~~~~~~~~~
Here is Clang's:
main.cpp:21:9: error: static_assert failed "T must have a value() method" static_assert(HasValueMethod<T>::value, "T must have a value() method"); ^
In general, there is a problem (as pointed out in the comments of @TC in the comments): MyClass now converted from something in terms of unvalued contexts. I.e
static_assert(std::is_convertible_v<, MyClass>);
In C ++ 20, when, hopefully, concepts exist, this is easily solved with the requires clause:
template <typename T> requires HasValueMethod<T>::value MyClass(const T &t) : m_value(t.value()) {}
You can directly express HasValueMethod<T> in the requires clause:
template <typename T> requires requires (T a) { { a.value() } -> int; } MyClass(const T &t) : m_value(t.value()) {}
Or converting HasValueMethod<T> to a real concept:
template <typename T> concept HasValueMethod = requires (T a) { { a.value() } -> int; };
Such solutions make std::is_convertible_v<T, MyClass> work as expected.