W.F. W.F. - 1 year ago 37
C++ Question

Can I pass a reference type to a template to specify following non-type template parameters' types?

Consider an example:

#include <type_traits>

template <class T, T>
struct has_duplicates_info { };

template <class T, T...>
struct has_duplicates;

template <class T, T First, T... Others>
struct has_duplicates<T, First, Others...>:
has_duplicates<T, Others...>,
has_duplicates_info<T, First> {
static constexpr bool value =
std::is_base_of<has_duplicates_info<T, First>, has_duplicates<T, Others...>>::value
|| has_duplicates<T, Others...>::value;

template <class T, T Last>
struct has_duplicates<T, Last>: has_duplicates_info<T, Last>, std::false_type { };

int a, b;

int main() {
static_assert(!has_duplicates<int, 0, 1, 2>::value, "has_duplicates<int, 0, 1, 2>::value");
static_assert(has_duplicates<int, 1, 2, 2, 3>::value, "!has_duplicates<int, 1, 2, 2, 3>::value");
static_assert(has_duplicates<int&, a, a, b>::value, "!has_duplicates<int&, a, a, b>::value");

This compiles fine with clang but not with gcc. The problem is in a line:

static_assert(has_duplicates<int&, a, a, b>::value, "has_duplicates<int&, a, a, b>::value");

where compiler suggests that
has_duplicates<int&, a, a, b>
is an incomplete type:

has_duplicates.cc:26:18: error: incomplete type ‘has_duplicates<int&, a, a, b>’ used in nested name specifier
static_assert(has_duplicates<int&, a, a, b>::value, "has_duplicates<int&, a, a, b>::value");

So... which compiler is right?


To clarify I am not trying to check if the runtime values behind variables passed to
contains duplicates only if there are duplicated references passed to this trait... E.g. the code below compiles successfully in both gcc and clang:

template <int &X>
struct a { };

int b;

int main() {
a<b> c;

Answer Source

First off, it's definitely a bug in gcc. You're nearly correct in your diagnosis of the nature of the bug, but it's not that gcc doesn't accept reference-type non-type template parameters, it's rather that gcc fails to recognise reference-type non-type template parameters as a class template partial specialization where the reference type is a previous template parameter:

template<int, class T, T> struct S;  // #1
template<class T, T A> struct S<0, T, A> {};  // #2
int i;
S<0, int&, i> s;  // #3 error: aggregate 'S<0, int&, i> s' has incomplete type

#2 is a perfectly legitimate partial specialization of #1 and should be matched by the instantiation #3, per [temp.class.spec.match] and [temp.deduct].

Fortunately, there's a simple workaround, which is to provide a further partial specialization for reference types:

template<class R, R& A> struct S<0, R&, A> {};

A correct compiler like clang will also be fine with this.

In your case the further partial specializations would be:

template <class R, R& First, R&... Others>
struct has_duplicates<R&, First, Others...> // ...
template <class R, R& Last>
struct has_duplicates<R&, Last> // ...