1
votes

At the root of my policy-based class lies a container adapter that provides an interface for conversion between different containers. It is parametrized by a type T and template template parameter Container. In order to make it work with standard containers I need to partially apply some of their arguments, for example an allocator, or array size. This is how I do it:

template< typename T >
struct vector_{
  using policy = std::vector<T>; //default allocator
}; 

or in the case that's giving me trouble:

 //data_adapter expects template template parameter that takes one type-argument,
 //but sadly std::array is a template<typename, size_t>
 //so we need to partially apply the size_t parameter
namespace array_detail{
  template< size_t N >
  struct array_impl{
    template< typename T >
    using array_default = std::array<T, N>;
  };  //now we can write array_impl<32>::array_default which is template<typename T>
}

The problem is that I need to partially specialize data_adaptor for all N of array_impl and GCC 4.8.1 seems not to consider my specialization when calling its constructor. Here is the code:

data_adapter.hpp:

//data_adapter.hpp
#ifndef __DATA_ADAPTER_HPP__
#define __DATA_ADAPTER_HPP__

#include <array>
#include <type_traits>

template <
  typename T,
  template< typename t >
  class Container
> class data_adapter
: protected Container< T >
{
protected:
  typedef Container<T> data_type;

public:
    //constructor forwarding
  using data_type::data_type;

    //const iterator access for cross-container conversion copying
  using data_type::cbegin;
  using data_type::cend;

  data_adapter() = default;

public:
  ~data_adapter() {}
};

  //SFINAE helper to test whether T is an iterator or not

template<typename T, typename = void>
struct is_iterator
{
   static constexpr bool value = false;
};

template<typename T>
struct is_iterator<T, typename std::enable_if<!std::is_same<typename std::iterator_traits<T>::value_type, void>::value>::type>
{
   static constexpr bool value = true;
};

  //data_adapter expects template template parameter that takes one type-argument,
  //but sadly std::array is a template<typename, size_t>
  //so we need to partially apply the size_t parameter
namespace array_detail{
  template< size_t N >
  struct array_impl{
    template< typename T >
    using array_default = std::array<T, N>;
  };  //now we can write array_impl<32>::array_default which is template<typename T>
}

//partial specialization for array_impl<N>::array_default for any N???

template< typename T, size_t N >
class data_adapter< T, array_detail::array_impl<N>::template array_default >
: protected array_detail::array_impl<N>::template array_default<T>
{
protected:
  typedef typename array_detail::array_impl<N>::template array_default<T> data_type;

public:
  using data_type::data_type;

  using data_type::cbegin;
  using data_type::cend;

    //why doesn't std::array implement this constructor?
    //is it because it has static size and conversion from a dynamic container is dangerous?

  template<
    typename InputIt,
    typename = typename
      std::enable_if<is_iterator<InputIt>::value>::type
  > data_adapter( InputIt begin, InputIt end )
  : data_type() {
    std::copy( begin, end, this->begin() );
  }

public:
  ~data_adapter() {}
};

//still doesn't work with explicit instantiation
//template class data_adapter< int, array_detail::array_impl<32>::template array_default >;

#endif // __DATA_ADAPTER_HPP__

main.cpp:

//main.cpp
#include <algorithm>
#include <ctime>
#include <cstdint>
#include <iostream>
#include <random>


#include "data_adapter.hpp"

int main()
{
  std::mt19937 generator(time(NULL));
  std::uniform_int_distribution<unsigned char> uniform_symbol( 0, 255 );
  auto random_symbol =
    [ &generator, &uniform_symbol ]( int ){
      return uniform_symbol(generator);
    };

  std::vector< int > symbols(32);
  std::transform( symbols.cbegin(), symbols.cend(), symbols.begin(), random_symbol );

  data_adapter< int, array_detail::array_impl<32>::template array_default > adapter( symbols.cbegin(), symbols.cend() );

  std::for_each( symbols.begin(), symbols.end(), []( int s ){ std::cout << s << " "; } );

  return 0;
}

errors:

g++.exe -Wall -fexceptions  -std=c++11 -g  -Wall    -c C:\Users\windows\Desktop\data_test\main.cpp -o obj\Debug\main.o
C:\Users\windows\Desktop\data_test\main.cpp: In function 'int main()':
C:\Users\windows\Desktop\data_test\main.cpp:23:119: error: no matching function for call to 'data_adapter<int, array_detail::array_impl<32u>::array_default>::data_adapter(std::vector<int>::const_iterator, std::vector<int>::const_iterator)'
   data_adapter< int, array_detail::array_impl<32>::template array_default > adapter( symbols.cbegin(), symbols.cend() );
                                                                                                                       ^
C:\Users\windows\Desktop\data_test\main.cpp:23:119: note: candidates are:
In file included from C:\Users\windows\Desktop\data_test\main.cpp:9:0:
C:\Users\windows\Desktop\data_test\data_adapter.hpp:25:3: note: data_adapter<T, Container>::data_adapter() [with T = int; Container = array_detail::array_impl<32u>::array_default]
   data_adapter() = default;
   ^
C:\Users\windows\Desktop\data_test\data_adapter.hpp:25:3: note:   candidate expects 0 arguments, 2 provided
C:\Users\windows\Desktop\data_test\data_adapter.hpp:11:9: note: constexpr data_adapter<int, array_detail::array_impl<32u>::array_default>::data_adapter(const data_adapter<int, array_detail::array_impl<32u>::array_default>&)
 > class data_adapter
         ^

It clearly does not consider the constructor I defined in the partial specialization for the array_impl, so the conclusion is that the data_adaptor in main.cpp instantiated the unspecialized version of the class. This is not a SFINAE problem, as the compiler would complain about lack of ::type in std::enable_if. Even, if I explicitly instantiate the specialized version, it still doesn't work. What am I doing wrong here?


EDIT: Here is a minimal version that I think is equivalent:

#include <iostream>
#include <array>

template<
  typename T,
  template < typename >
  class Container
> struct Foo{
  void test() const {
    std::cout << "Unspecialized Foo" << std::endl;
  }
};

template< size_t N >
struct array_{
  template< typename T >
  using policy = std::array<T, N>;
};

template<
  typename T, size_t N
> struct Foo< T, array_<N>::template policy >{
  void test() const {
    std::cout << "Foo< T, array<" << N << ">::policy >" << std::endl;
  }
};

int main()
{
    Foo< int, array_<10>::template policy > foo;
    foo.test();
    return 0;
}

Result at GCC 4.8.1:

Unspecialized Foo

Why is the unspecialized version called?


EDIT 2: It seems to work when I write it this way:

#include <iostream>
#include <array>

template<
  typename container
> struct Foo{
  void test() const {
    std::cout << "Unspecialized Foo" << std::endl;
  }
};

template<
  typename T, size_t N
> struct Foo< std::array<T, N> >{
  void test() const {
    std::cout << "Foo< std::array< T, " << N << ">" << std::endl;
  }
};

int main()
{
    Foo< std::array<int, 10> > foo;
    foo.test();
    return 0;
}

Looks like a GCC bug to me.

1
@R.MartinhoFernandes That won't work for std::array which has an integer template argument (but it would solve the issue for std::vector etc. which all have additional template arguments beyond the value type).Seg Fault
"why doesn't std::array implement this constructor?" Because if it had a user-provided constructor then it would no longer be an aggregate and couldn't be initialized using aggregate initialization.T.C.
Considering that g++ and clang agree on your original code, I'm somewhat doubtful that this is a compiler bug.T.C.
I believe that g++'s behavior is correct. Partial specialization matching is done using template argument deduction, and anything to the left of :: is a non-deduced context, which means that the compiler cannot deduce the N in your partial specialization, and hence the base template is used.T.C.

1 Answers

4
votes

This is not a compiler bug. §14.5.5.1 [temp.class.spec.match]/p1-2 specifies that

When a class template is used in a context that requires an instantiation of the class, it is necessary to determine whether the instantiation is to be generated using the primary template or one of the partial specializations. This is done by matching the template arguments of the class template specialization with the template argument lists of the partial specializations.

  • If exactly one matching specialization is found, the instantiation is generated from that specialization.
  • If more than one matching specialization is found, the partial order rules (14.5.5.2) are used to determine whether one of the specializations is more specialized than the others. If none of the specializations is more specialized than all of the other matching specializations, then the use of the class template is ambiguous and the program is ill-formed.
  • If no matches are found, the instantiation is generated from the primary template.

A partial specialization matches a given actual template argument list if the template arguments of the partial specialization can be deduced from the actual template argument list (14.8.2).

This uses the standard template argument deduction rules, and anything to the left of :: (the technical term is the nested-name-specifier in a qualified-id) is a non-deduced context (§14.8.2.5 [temp.deduct.type]/p5), meaning that the compiler won't be able to deduce N; since deduction fails, the partial specialization is not a match, and the base template is used.