0

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?

Dr. Gut
  • 2,053
  • 7
  • 26
  • `class = std::enable_if_t && dummy>` what is this supposed to do/mean? I can't really make any sense of it and it's clearly not well-formed code. – super Sep 19 '20 at 18:53
  • 1
    Maybe one of the reasons you're getting downvoted is also that your question is a bit confusing. There is more explanation then code showing what you want. Your description with `Foo` `Bar` and `Baz` and `HasX` can easily be misinterpreted. Just show code for what you want. Code is much easier to understand then explanations of code. – super Sep 19 '20 at 18:57
  • I have more questions. Let's say we have a type with methods `Foo` and `Bar` but not `Baz`. Then we can already call `Foo()` and `Bar()` on it, but `Baz()` will fail with a compile time error. So what exactly is the `Wrapper` supposed to do or there for? You could just inherit from the passed in class and you would automatically get that behaviour. Which contributes to making this question more unclear what you actually want to achieve. – super Sep 19 '20 at 19:01
  • @super: I have edited the question. Hopefully it is better now. `Wrapper` cannot inherit form `Type`, as `Type` may be `final` or not even a class. As an example `Wrapper` might optionally contain a `Type`, and `Foo`, `Bar`, `Baz` could be comparison operators, which should exist on `Wrapper` iff they exist on `Type`. – Dr. Gut Sep 20 '20 at 13:29

1 Answers1

1

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.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207