I have a question about template specialization in conjunction with variadic templates and template template parameters/arguments.
The following little program compiles with Clang 6.0.1
and GCC 8.1.1
(target: x86_64-pc-linux-gnu
).
#include <iostream>
struct IndexSlot3 {
static constexpr std::size_t size = 3;
};
template<typename T_, typename... IndexSlots_>
struct Tensor {
Tensor() { std::cout << "Order N picked" << std::endl; }
};
template<typename T_, typename IndexSlot0_>
struct Tensor<T_, IndexSlot0_> {
Tensor() { std::cout << "Order 1 picked" << std::endl; }
};
template<typename T_, typename IndexSlot0_, typename IndexSlot1_>
struct Tensor<T_, IndexSlot0_, IndexSlot1_> {
Tensor() { std::cout << "Order 2 picked" << std::endl; }
};
int main(int argc, char** argv) {
Tensor<int, IndexSlot3> t1;
Tensor<int, IndexSlot3, IndexSlot3> t2;
Tensor<int, IndexSlot3, IndexSlot3, IndexSlot3> t3;
return 0;
}
Compile commands:
clang++ -std=c++17 main.cpp -o main -Wall -Wpedantic
g++ -std=c++17 main.cpp -o main -Wall -Wpedantic
Running the program prints
Order 1 picked
Order 2 picked
Order N picked
with both compilers, as expected.
However, when I extend the above to the following program, where Tensor
also takes a template template parameter defining storage, Clang picks the primary template in all three cases, while GCC still picks the specializations.
#include <iostream>
struct IndexSlot3 {
static constexpr std::size_t size = 3;
};
template<typename T_, std::size_t... sizes>
struct CArrayStorage {
using Type = T_*;
};
template<typename T_, std::size_t size0_>
struct CArrayStorage<T_, size0_> {
using Type = T_[size0_];
};
template<typename T_, std::size_t size0_, std::size_t size1_>
struct CArrayStorage<T_, size0_, size1_> {
using Type = T_[size0_][size1_];
};
template<typename T_,
template<typename, std::size_t...> typename Storage_,
typename... IndexSlots_>
struct Tensor {
typename Storage_<T_, IndexSlots_::size...>::Type _storage;
Tensor() { std::cout << "Order N picked" << std::endl; }
};
template<typename T_,
template<typename, std::size_t> typename Storage_,
typename IndexSlot0_>
struct Tensor<T_, Storage_, IndexSlot0_> {
typename Storage_<T_, IndexSlot0_::size>::Type _storage;
Tensor() { std::cout << "Order 1 picked" << std::endl; }
};
template<typename T_,
template<typename, std::size_t, std::size_t> typename Storage_,
typename IndexSlot0_, typename IndexSlot1_>
struct Tensor<T_, Storage_, IndexSlot0_, IndexSlot1_> {
typename Storage_<T_, IndexSlot0_::size, IndexSlot1_::size>::Type _storage;
Tensor() { std::cout << "Order 2 picked" << std::endl; }
};
int main(int argc, char** argv) {
Tensor<int, CArrayStorage, IndexSlot3> t1;
Tensor<int, CArrayStorage, IndexSlot3, IndexSlot3> t2;
Tensor<int, CArrayStorage, IndexSlot3, IndexSlot3, IndexSlot3> t3;
return 0;
}
Output with Clang:
Order N picked
Order N picked
Order N picked
Output with GCC:
Order 1 picked
Order 2 picked
Order N picked
I think GCC is right, because the specialization and the primary match equally well, so the specialization should be picked. But I'm not entirely sure how the introduction of the template template parameter changes things.
Should I file a bug against Clang? Against GCC? Against both? Have I wandered into the realm of undefined behaviour without realizing it?
Any help would be greatly appreciated.
[The examples aren't exactly minimal, but I wanted to provide a little bit of context, so you can understand what I'm trying to do.]