Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ nested template issue

GCC 7.3.1 compile the below code while clang 8.0.0 does not. I would like to know if this syntax is valid (in which case I will report it as a possible clang bug).

Thanks for your help.

template<typename FOO>
struct Foo
{
  using Value = int;

  template<Value VALUE>
  struct Bar;
};

template<typename FOO>
template<typename Foo<FOO>::Value VALUE>
struct Foo<FOO>::Bar { static void test(); };

template<typename FOO>
template<typename Foo<FOO>::Value VALUE>
void Foo<FOO>::Bar<VALUE>::test() {}

int main() { return 0; }

The error message with clang is the following:

error: nested name specifier 'Foo<FOO>::Bar<VALUE>::' for declaration does not refer into a class, class template or class template partial specialization
void Foo<FOO>::Bar<VALUE>::test() {}
     ~~~~~~~~~~~~~~~~~~~~~~^
1 error generated.

EDIT: clang possible bug report here.

like image 691
Ankur deDev Avatar asked Mar 25 '19 09:03

Ankur deDev


People also ask

What is the main problem with templates C++?

C++ templates can't use normal run-time C++ code in the process of expanding, and suffer for it: for instance, the C++ factorial program is limited in that it produces 32-bit integers rather than arbitrary-length bignums.

Do templates slow down C++?

The short answer is no. Indirectly, however, they can slow things down under a few circumstances. In particular, each instantiation of a template (normally) produces code that's separate and unique from other instantiations.

Are templates supported in C?

The main type of templates that can be implemented in C are static templates. Static templates are created at compile time and do not perform runtime checks on sizes, because they shift that responsibility to the compiler.

Are C++ templates type safe?

C++ templates are checked at least twice. First, when a template is declared & defined, second when it is instantiated. After a template successfully instantiated it is in a type safe state.


2 Answers

From [temp.mem.class/1], we have

A member class of a class template may be defined outside the class template definition in which it is declared.

Furthermore, in a non-template context, [class.nest/2] tells us:

Member functions and static data members of a nested class can be defined in a namespace scope enclosing the definition of their class.

Let's hence construct a simpler example and verify that the definition of a member function of a nested type is allowed to be separated from the definition of the nested, non-template type itself. In analogy to the types in your snippet:

template <class FOO>
struct Foo {
   // Simpler, Bar is not a template
   struct Bar;
};

// Definition of Bar outside of Foo as before
template <class FOO>
struct Foo<FOO>::Bar {
   static void test(); 
};

And now the critical part, the definition of Bar::test() outside of Bar itself:

template <class FOO>
void Foo<FOO>::Bar::test() { }

This happily compiles with both gcc-8 and clang (trunk as well as a much older stable version).

I might be misunderstanding something here, but my conclusion is that the syntax to define Foo::Bar::test() outside of Foo and outside of Bar is indeed fine, and clang should compile it as gcc does.

like image 133
lubgr Avatar answered Oct 15 '22 15:10

lubgr


This is an interesting case! My position whether it is a compiler or standard problem is similar to @lubgr, but I wanted to add some more insights.

ICC also have some problems with your construct, which might suggest that this is more deeply rooted in standard (still, gcc might be correct here). It fails with error: "template argument list must match the parameter list" - this might mean that for both compilers this:

template<typename FOO>
template<typename Foo<FOO>::Value VALUE>

are not identical with original definition of Foo. It seems to be a bug of both compilers, but I've learned to be cautious when two different compilers share similar problems.

Extracting definition of Value from original template to separate one fixes the case (code on Compiler Explorer):

template<typename T>
struct X
{
    using Value = int;
};

template<typename FOO>
struct Foo
{    
  template<typename X<FOO>::Value VALUE>
  struct Bar;
};

template<typename FOO>
template<typename X<FOO>::Value VALUE>
struct Foo<FOO>::Bar { static void test(); };

template<typename FOO>
template<typename X<FOO>::Value VALUE>
void Foo<FOO>::Bar<VALUE>::test() {}

int main() { return 0; }

You can fix this as well by simply using hardcoded Value type (code on Compiler Explorer) - but this is not what you need probably:

template<typename FOO>
struct Foo
{    
  template<int VALUE>
  struct Bar;
};

template<typename FOO>
template<int VALUE>
struct Foo<FOO>::Bar { static void test(); };

template<typename FOO>
template<int VALUE>
void Foo<FOO>::Bar<VALUE>::test() {}

int main() { return 0; }

Hope it helps!

like image 1
Michał Łoś Avatar answered Oct 15 '22 17:10

Michał Łoś