7

I am trying to create partially specialized template, and have specialize even further if passed a std::unique_ptr

template <typename T, typename = void>
struct Foo;

// A
template <typename T>
struct Foo<std::unique_ptr<T>, typename std::enable_if<std::is_class<T>::value>::type> {...};

// B
template <typename T>
struct Foo<T, typename std::enable_if<std::is_class<T>::value>::type> {...};

void fn() {
    Foo<std::unique_ptr<T>> foo;
}

My understanding is that A is more specialized than B, and should be the one used. But at least in MSVC 2015 I get the error:

error C2752: 'Foo<FieldT,void>': more than one partial specialization matches the template argument list

Is there something I'm missing here?

max66
  • 65,235
  • 10
  • 71
  • 111
Arelius
  • 1,216
  • 8
  • 15

3 Answers3

5

Question: which specialization should be used calling foo<std::unique_ptr<int>>?

Observe that int isn't a class, so the value std::is_class<T>::value is false and the A version doesn't match.

If you want that the A version is ever used when the first template parameter is a std::unique_ptr, you can write the A version as

template <typename T>
struct foo<std::unique_ptr<T>, 
           typename std::enable_if<std::is_class<
              std::unique_ptr<T>>::value>::type>
 { static int const value = 1; };

or you can write foo as follows

template <typename T,
          typename = typename std::enable_if<std::is_class<T>::value>::type> 
struct foo;

template <typename T>
struct foo<std::unique_ptr<T>>
 { static int const value = 1; };

template <typename T>
struct foo<T>
 { static int const value = 2; };

So A become a more specialized version than B and your code can compile. Otherwise your version of A is, in fact, a more specialized version that B but the compiler doesn't recognize it.

Observe that with

template <typename T>
struct foo<std::unique_ptr<T>, 
           typename std::enable_if<std::is_class<
              std::unique_ptr<T>>::value>::type>
 { static int const value = 1; };

the code compile and that std::is_class<std::unique_prt<T>>::value is ever true; but if you write

template <typename T>
struct foo<std::unique_ptr<T>, 
           typename std::enable_if<true>::type>
 { static int const value = 1; };

(that, in fact, is equivalent) the code doesn't compile

-- EDIT --

If you want that foo<std::unique_ptr<int> match case B, you can (by example) create a type traits as follows

struct foo<T>
 { static int const value = 2; };

template <typename T>
struct proValue
   : std::integral_constant<int, 2>
 { };

template <typename T>
struct proValue<std::unique_ptr<T>>
   : std::integral_constant<int, std::is_class<T>::value ? 1 : 2>
 { };

and define foo as follows

template <typename T, int = proValue<T>::value>
struct foo;

template <typename T>
struct foo<std::unique_ptr<T>, 1>
 { static int const value = 1; };

template <typename T>
struct foo<T, 2>
 { static int const value = 2; };

The following is a full compilable example

#include <memory>
#include <vector>
#include <iostream>
#include <type_traits>

class Test
 { };

template <typename T,
          typename = typename std::enable_if<std::is_class<T>::value>::type> 
struct foo;

template <typename T>
struct foo<std::unique_ptr<T>>
 { static int const value = 1; };

template <typename T>
struct foo<T>
 { static int const value = 2; };

template <typename T>
struct proValue
   : std::integral_constant<int, 2>
 { };

template <typename T>
struct proValue<std::unique_ptr<T>>
   : std::integral_constant<int, std::is_class<T>::value ? 1 : 2>
 { };

template <typename T, int = proValue<T>::value>
struct bar;

template <typename T>
struct bar<std::unique_ptr<T>, 1>
 { static int const value = 1; };

template <typename T>
struct bar<T, 2>
 { static int const value = 2; };

int main ()
 {
   std::cout << foo<std::vector<int>>::value << '\n';      // print 2
   std::cout << foo<std::unique_ptr<int>>::value << '\n';  // print 1
   std::cout << foo<std::unique_ptr<Test>>::value << '\n'; // print 1

   std::cout << bar<std::vector<int>>::value << '\n';      // print 2
   std::cout << bar<std::unique_ptr<int>>::value << '\n';  // print 2
   std::cout << bar<std::unique_ptr<Test>>::value << '\n'; // print 1
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • Ahh I see. Basically the compiler is trying to ensure that one version is more specialized on all arguments than another, and since the type of `std::enable_if::value>::type` is different than `std::enable_if>::value>::type` specialization comparison fails! – Arelius Jun 18 '17 at 19:23
2

I set up a small example to reproduce the error a little nicer.

#include <iostream>
#include <memory>
#include <type_traits>

template < typename T, typename = void >
struct foo;

template < typename T >
struct foo < std::unique_ptr<T>,
             typename std::enable_if<std::is_class<T>::value>::type >
{
  static int const value = 1;
};

template < typename T >
struct foo < T,
             typename std::enable_if<std::is_class<T>::value>::type >
{
  static int const value = 2;
};

class Test;

int main()
{
  std::cout << foo< std::unique_ptr<Test> >::value << '\n';
}

I think the error from Clang pretty unambiguous

test.cpp:26:16: error: ambiguous partial specializations of
      'foo<std::unique_ptr<Test, std::default_delete<Test> >, void>'
  std::cout << foo< std::unique_ptr<Test> >::value << '\n';
               ^
test.cpp:9:8: note: partial specialization matches [with T = Test]
struct foo < std::unique_ptr<T>,
       ^
test.cpp:16:8: note: partial specialization matches [with T = std::unique_ptr<Test,
      std::default_delete<Test> >]
struct foo < T,
       ^
1 error generated.

How should the compiler know that you want to have deduced T = Test with the first specialization rather than T = std::unique_ptr<Test>? Also, in both cases T is a class which makes the std::enable_if meaningless.

Henri Menke
  • 10,705
  • 1
  • 24
  • 42
  • I think the question was why is it ambiguous – Passer By Jun 18 '17 at 09:41
  • @PasserBy the answer is in the last two sentences: it is ambiguous because there's nothing the compiler can use to disambiguate the two cases: they're both equally valid. – rubenvb Jun 18 '17 at 10:26
  • @rubenvb the problem was *My understanding is that A is more specialized than B, and should be the one used*, which this doesn't answer. – Passer By Jun 18 '17 at 10:28
  • @PasserBy: Well then the answer is: no, it's not more specialized, followed by the rest of the answer. – rubenvb Jun 18 '17 at 10:54
  • 2
    It is more specialized, if both `enable_if` clauses are removed. So the question is: why does adding `enable_if` subvert the partial ordering of specializations? – Oktalist Jun 18 '17 at 13:38
  • As hinted at in max66's answer, it seems the problem is since the enable_if cases don't match after substitution then the templates don't match and neither is more specialized. – Arelius Jun 18 '17 at 19:28
1

In addition to what Henri said in his answer, you can (in a limited sense) work around this by writing a near-trivial is_unique_ptr trait.

Live demo here.

I omitted the code because it's fundamentally flawed.


You can see how this already fails for a non-trivial std::unique_ptr, but that can be resolved by extending the is_unique_ptr trait. Note that implementations are very much allowed to add extra (defaulted) template parameters as they wish, so this will never be a watertight.

A more proper solution involves also modifying your foo template specialization:

#include <iostream>
#include <memory>
#include <type_traits>

template<typename T>
struct is_unique_ptr : std::false_type {};

template<typename... UniquePtrArgs>
struct is_unique_ptr<std::unique_ptr<UniquePtrArgs...>> : std::true_type {};

template<typename T, typename = void>
struct foo;

template<typename... UniquePtrArgs>
struct foo<std::unique_ptr<UniquePtrArgs...>>
{
  static int const value = 1;
};

template<typename T>
struct foo<T, typename std::enable_if<!is_unique_ptr<T>::value && std::is_class<T>::value>::type>
{
  static int const value = 2;
};

class Test;

void f(Test*);

int main()
{
  std::cout << foo<std::unique_ptr<Test>>::value << '\n';
  std::cout << foo<Test>::value << '\n';
  std::cout << foo<std::unique_ptr<Test, decltype(&f)>>::value << '\n';
}

Live demo here.

Instead of specializing for std::unique_ptr, it might be more sensible to specialize on types with a certain property of std::unique_ptr you're leveraging in the specialization, so you're not tied to a specific type, but rather a specific property.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • Thanks, I ended up solving the problem with making my unique_ptr case also have that in the comparison, and then changing the implementation to not care that the underlying T is not a class. But this could have been good too. – Arelius Jun 18 '17 at 19:27