How to eliminate this pattern?

I have a class that takes size as a template parameter ( live demo ):

template <std::size_t SIZE> class A { char b[SIZE]; } 

It has several constructors for different purposes:

 using const_buffer_t = const char (&)[SIZE]; using my_type = A<SIZE>; A() : b{} {} // (1) no params A(const_buffer_t) : b{} {} // (2) copy contents of given buffer A(const char * const) : b{} {} // (3) copy as many items as they fit into the size explicit A(const my_type &) : b{} {} // (4) copy constructor // (5) copy as many items as they fit into the size template <std::size_t OTHER_SIZE> A(const char (&)[OTHER_SIZE]) : b{} {} // (6) copy constructor from another sized A // copy as many items as they fit into the size template <std::size_t OTHER_SIZE> explicit A(const A<OTHER_SIZE> &) : b{} {} 

There are no problems with this instruction set with this instruction set:

 // CASE 1 // Calls constructor 3: A<5>(const char * const) // Expecting constructor 5: A<5>(const char (&)[11]) A<5> a("0123456789"); // CASE 2 // As expected, calls constructor 1: A<5>() A<5> b(); // CASE 3 // As expected, calls constructor 4: A<5>(const A<5> &) A<5> c(b); // CASE 4 // As expected, calls constructor 6: A<5>(const A<9> &) A<9> c(b); 

But when calling A<5>("five") an ambiguous call arises between constructors 2, 3, 4, and 5.

So my questions are:

  • Why is constructor 3 preferable to constructor 5 in CASE 1 ?
  • Is there a way to eliminate the ambiguity of constructors 2, 3, 4, 5 when the object A<SIZE> built with a static array of the same size of the template parameter?

Thanks for your attention.

+4
c ++ c ++ 11 templates disambiguation
Apr 25 '14 at 10:53 on
source share
2 answers

Converting an array to a pointer is considered an exact match when ranking conversion sequences during overload resolution (C ++ 11 13.3.3.1.1 / 1 Table 12). Unlike your intuition, this means that (3) and (5) are equally good for A<5> a("0123456789"); . The tie is broken - as Xeo says in his comment - in favor of a non-template (3). You might think to trick the compiler by turning (3) into a template:

 template <typename=void> A(const char * const) : b{} {} 

but it will only lead to ambiguity in the design . There is actually no easy way to eliminate the overloads of const char (&)[] and const char* : a better solution would be to change (3) to accept a pointer and length:

 A(const char * const, std::size_t) : b{} { std::cout << "size: " << SIZE << " ctor 3\n"; } 

I will try to note that adding the size_t argument to the const char* const constructor also removes the ambiguity of the A("five") case .

EDIT: There is one sensible way to decouple the char* constructor from the array constructor , to take pointer arguments by reference:

 template <typename T, typename=typename std::enable_if< std::is_same<typename std::remove_cv<T>::type, char>{} >::type> A(T* const&) : b{} { std::cout << "size: " << SIZE << " ctor 3\n"; } 

[Credit for this trick goes dyp , and possibly Johannes Schaub or Jakka or me (I'm sure it was not me).

This template is effectively fixed to the actual type by reference - until the array is converted to a pointer, and then restrains references to types without pointers.

+3
Apr 25 '14 at 14:25
source share
  • Why is constructor 3 preferred over constructor 5 in CASE 1?

    Answer: Due to permit overload . Non templated class functions are first class citizens and, as such, have a higher ranking of overload resolution than boilerplate functions. Thus, constructor 3 is preferred over template constructor 5.

+2
Apr 25 '14 at 12:04 on
source share



All Articles