Reputation: 2836
Suppose types can have Foo
, Bar
, Baz
methods, and we have type traits to check it. E.g. for Foo
we have HasFoo
:
template <class Type>
constexpr bool DetectFoo (decltype (std::declval<Type> ().Foo ())*) { return true; }
template <class Type>
constexpr bool DetectFoo (...) { return false; }
template <class Type>
constexpr bool HasFoo = DetectFoo<Type> (nullptr);
Let's write a Wrapper<Type>
class template, that forwards the property of Type
having or not having these methods. For any Type
the following should be satisfied:
Wrapper<Type> w;
should compile, since we did not call the methods yet.w.X ();
should compile iff HasX<Type>
, for X
= Foo
, Bar
, Baz
.HasX<Wrapper<Type>> == HasX<Type>
should hold, for X
= Foo
, Bar
, Baz
.To conditionally enable a method in Wrapper
there is a clear way in C++20:
template <class Type>
struct Wrapper {
void Foo ()
requires HasFoo<Type>;
};
But what to do in earlier C++ standards, without concepts? I have come up with the following idea (see live demo):
template <class Type>
struct Wrapper {
template <bool dummy = true, class = std::enable_if_t<HasFoo<Type> && dummy>>
void Foo ();
};
This answer says it is ill-formed, NDR, but I don't understand the explanation. Is this really ill-formed, NDR?
Upvotes: 0
Views: 310
Reputation: 96286
Your code is well-formed.
The linked answer refers to [temp.res.general]/8.1
:
The validity of a template may be checked prior to any instantiation. ... The program is ill-formed, no diagnostic required, if:
— no valid specialization can be generated for a template ... and the template is not instantiated, ...
Here, the "template" that we're talking about is Foo
.
I believe this can be interpreted in two ways:
(1) We can consider Wrapper<A>::Foo
and Wrapper<B>::Foo
to be the same template (for every A
,B
). Then, the existence of such a template argument for Wrapper
that makes the condition in enable_if_t
true
is alone enough to make the code well-formed.
(2) We can also consider Wrapper<A>::Foo
and Wrapper<B>::Foo
to be different templates (for A != B
). Then, if there existed such a template argument for Wrapper
for which it's impossible to instantiate Foo
, your code would be ill-formed NDR. But it never happens, because you can always specialize HasFoo
to be true
for every template argument!
In any case, I argue that (1) is the intended interpretation. The purpose of [temp.res.general]/8.1
is not to get in your way, but to help you by validating templates early when possible. I've never seen compilers use the second interpretation.
Upvotes: 1