Dr. Gut
Dr. Gut

Reputation: 2836

Conditionally enabled member functions in C++17

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:

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

Answers (1)

HolyBlackCat
HolyBlackCat

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

Related Questions