8

The question may be too hard to describe in on sentence in the title, but here is a minimal example:

#include <iostream>
#include <type_traits>

template <class T, class U, class Enabler>
struct my_trait : std::false_type
{};

template <class T, class U>
struct my_trait<T, U, 
                std::enable_if_t<std::is_same<T, U>::value>> : std::true_type
{};

template <class T>
class temped
{};

template <class T>
struct my_trait<temped<T>, temped<T>, void> : std::false_type
{};

template <class T, class U>
using trait_t = my_trait<T, U, void>;

int main()
{
    std::cout << std::boolalpha;
    std::cout << trait_t<int, float>::value << std::endl;   // false
    std::cout << trait_t<int, int>::value << std::endl;     // true

    // Compilation error: Ambiguous
    //std::cout << trait_t<temped<int>, temped<int>>::value << std::endl;
    return 0;    
}

(also available on godbolt)

Basically, we have a base template class my_trait taking two types (and a dummy type for specialization purposes), with two partial specializations:

  • When the two types are the same
  • When the two types are instantiation of the temped class template for the same type

Naïvely, we would have expected the second partial specialization not to be ambiguous with the first, as it feels "more specialized", putting more restriction on the deduced types for T and U on the base template. Yet major compilers seems to agree that we were wrong with our expectations: why is it not considered more specialized?

Ad N
  • 7,930
  • 6
  • 36
  • 80

2 Answers2

1

@super's now-deleted answer got this basically right. std::enable_if_t<...> is not void in partial ordering; as a dependent type it can in principle be something completely arbitrary. It effectively is considered a completely unique type for partial ordering purposes.

As a result of this mismatch, the deduction during partial ordering fails in both directions, and the specializations are ambiguous.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 1
    But it's not really a dependent type when doing partial ordering, is it? It's `std::enable_if_t::value>` where `DummyT` is a theoretical-only "synthesized" but specific type ([\[temp.func.order\]/3](https://timsong-cpp.github.io/cppwp/temp.func.order#3)). – aschepler Nov 21 '19 at 14:02
  • Maybe a "synthesized unique type" also implies that there might be additional specializations using that type, even if no currently visible specializations could make a difference? But does the Standard say that anywhere? – aschepler Nov 21 '19 at 14:17
  • I may be missing your point: the `std_enable_if_t<>` provides a preferred partial specialization in the statement `trait_t::value`, so it looks like it is considered `void` (otherwise, it seems this expression should evaluate to false by selecting the base template). On the other hand, the statement `trait_t, temped>::value` leads to an ambiguity, although the partial specialization `my_trait, temped, void>` we hoped would be more specialized explicitly states the `void` type. – Ad N Nov 21 '19 at 15:05
  • There is an open issue that is related to this one: http://wg21.link/CWG2160 However, in that case, deduction apparently does succeed in one direction (?) so I'm not able to reconcile that example with this one. – Brian Bi Nov 21 '19 at 16:10
  • More compiler results that seem non-uniform: https://stackoverflow.com/q/31394260/9171697 – ph3rin Nov 21 '19 at 19:03
  • @KaenbyouRin indeed, if Bogdan's claim is true: that `P` is skipped when it's a non-deduced context and "success" is returned for that P/A pair (in Clang), then deduction should succeed here, in one direction. I don't understand why Clang would reject the code here. – Brian Bi Nov 21 '19 at 20:08
  • 1
    And in any case, the "real" answer to OP's question should be: the standard is not clear on this, so don't write code like this until the standard clarifies it. – Brian Bi Nov 21 '19 at 20:10
0

it's because

std::enable_if_t<std::is_same_v<temped<int>, temped<int>> 

resolves to void and then you have

my_trait<temped<int>, temped<int>, void>::value 

defined ambigiously as true or false. If you change the type enable_if resolves to, let's say bool, everything compiles fine

Yamahari
  • 1,926
  • 9
  • 25
  • 1
    The OP is asking why it is ambiguous, under the context of template specialization partial ordering. If you change to `std::enable_if_t<*, bool>`, then it kind of defeat the purpose (because it is not even enabled for specialization). – ph3rin Nov 20 '19 at 18:19