简体   繁体   中英

Conditionally enabled member functions in C++17

Suppose types can have Foo , Bar , Baz methods, and we have type traits to check it. Eg 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.
  • wX (); 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?

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM