3

With the following template and specialization:

template<typename T, typename = void>
struct A {
    void operator()() {
        std::cout << "primary" << std::endl;
    }
};

template<typename T>
struct A<T, decltype(T().f())> {
    void operator()() {
        std::cout << "specialization" << std::endl;
    }
};

used like this:

struct X {
    bool f() { return false; }
};

int main()
{
    A<X>()();
    return 0;
}

the primary template is resolved, when one would expect partial specialisation to be selected. However, when changed to:

template<typename T>
struct A<T, typename std::enable_if<std::is_object<decltype(T().f())>::value>::type> {
    void operator()() {
        std::cout << "specialization" << std::endl;
    }
};

the specialization is chosen. Why isn't specialization chosen in the original example?

trozen
  • 1,117
  • 13
  • 13
  • @Rakete1111 I don't think that's a duplicate. That duplicate describes an ill-formed template parameter. This question involves a well-formed specialization that simply doesn't specialize the test usage. – Drew Dormann Sep 27 '18 at 20:46
  • Waited to see a "reopen" vote or a rebuttal before casting my "reopen" vote... – Drew Dormann Sep 27 '18 at 20:55
  • @DrewDormann That wouldn't matter anyways, but that's not true. The duplicate mentions both, and asks about why the specialization is not chosen for `int`. Here, it's the same question: Why is the specialization not chosen even though the expression is valid for the given types. – Rakete1111 Sep 27 '18 at 20:55
  • For the record, [this](https://stackoverflow.com/questions/44858395) was the claimed duplicate. I will let the crowd decide, rather than try to be objective. Cheers! – Drew Dormann Sep 27 '18 at 20:59

1 Answers1

3

You have instantiated:

A<X>

Which leverages a default template parameter to become:

A<X,void>

Your specialization, however, is:

template<typename T>
struct A<T, decltype(T().f())>

Which, for template parameter X becomes:

struct A<X, decltype(X().f())>

Which is:

struct A<X, bool>

So, your specialization is not the correct choice, because A<X,bool> does not specialize A<X,void>.


If you want your specialization to work for all well-formed instances of T().f(), you can use C++17's std::void_t

std::void_t will evaluate to void for any well-formed template parameters passed to it.

template<typename T>
struct A<T, std::void_t<decltype(T().f())>> {
    void operator()() {
        std::cout << "specialization" << std::endl;
    }
};
Rakete1111
  • 47,013
  • 16
  • 123
  • 162
Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
  • It seems like it's worth mentioning how you can fix this with things before C++17, i.e. by adding `,void()` to the `decltype` call, like `decltype(T().f(), void())` – Shep Nov 20 '18 at 14:36