Home > front end >  Understanding templates defined inside templates (C )
Understanding templates defined inside templates (C )

Time:09-07

According to my understanding, the following two programs should be effectively identical, but the second fails to compile. Note that in this simplified example, the nesting is obviously useless, but it's necessary in the original context.

Version 1: works as expected

#include <iostream>                                                                                                                      
                                                                                                                                         
template<typename Value>                                                                                                                 
struct Anything {                                                                                                                        
    Value value;                                                                                                                         
};                                                                                                                                       
                                                                                                                                         
using A = const char *;                                                                                                                  
                                                                                                                                         
template<template<typename> class B>                                                                                                     
struct Inner {                                                                                                                           
    template<typename C>                                                                                                                 
    using type = B<C>;                                                                                                                   
};                                                                                                                                       
                                                                                                                                         
template<template<typename> class B>                                                                                                     
struct OuterBase {                                                                                                                       
    B<A> b;                                                                                                                              
                                                                                                                                         
    void                                                                                                                                 
    print() { std::cout << "b: " << b.value << std::endl; }                                                                              
};                                                                                                                                       
                                                                                                                                         
using Outer = OuterBase<Inner<Anything>::type>; // this version works                                                                                          
                                                                                                                                         
int                                                                                                                                      
main()                                                                                                                                   
{                                                                                                                                        
    Outer outer;                                                                                                                         
    outer.b.value = "foo";                                                                                                               
    outer.print();                                                                                                                       
                                                                                                                                         
    return 0;                                                                                                                            
} 

The compiler error in this version is "expected a template, got TypeBox<A>::Inner<Anything>::type".

Version 2: fails with compile error

#include <iostream>                                                                                                                      
                                                                                                                                         
template<typename Value>                                                                                                                 
struct Anything {                                                                                                                        
    Value value;                                                                                                                         
};                                                                                                                                       
                                                                                                                                         
template<typename A>                                                                                                                     
struct TypeBox {                                                                                                                         
    template<template<typename> class B>                                                                                                 
    struct Inner {                                                                                                                       
        template<typename C>                                                                                                             
        using type = B<C>;                                                                                                               
    };                                                                                                                                   
                                                                                                                                         
    template<template<typename> class B>                                                                                                 
    struct OuterBase {                                                                                                                   
        B<A> b;                                                                                                                          
                                                                                                                                         
        void                                                                                                                             
        print() { std::cout << "b: " << b.value << std::endl; }                                                                          
    };                                                                                                                                   
                                                                                                                                         
    using Outer = OuterBase<Inner<Anything>::type>; // compile error here                                                                                      
};                                                                                                                                       
                                                                                                                                         
int                                                                                                                                      
main()                                                                                                                                   
{                                                                                                                                        
    using Box = typename TypeBox<const char *>::Outer;                                                                                   
                                                                                                                                         
    Box box;                                                                                                                             
    box.b.value = "foo";                                                                                                                 
    box.print();                                                                                                                         
                                                                                                                                         
    return 0;                                                                                                                            
}    

I would expect the TypeBox struct to be transparent when resolving the types inside, but according to gcc 12.2.1, it isn't. Why not?

CodePudding user response:

The difference in your second example is that a compiler cannot verify at the point of definition of the template that Inner<Anything>::type is really a class template or alias template. The problem is that Inner is a nested class template and you could in theory partially or explicitly specialize Inner for the given template argument later, so that type names e.g. a type.

So compilers tend to require that you place the template keyword before the template's name in such a case to state that it really is a template:

using Outer = OuterBase<Inner<Anything>::template type>;                                                                                      

My understanding is that technically there is nothing in the standard requiring this use of template though, but that this wasn't entirely clear. However, the standard did allow using the template keyword in this position, but will deprecate this allowance with C 23. If I am not mistaken compilers should also allow this without template keyword in the future even for previous standard revisions (as defect report). There wasn't ever really a point in performing this test.

See also CWG issue 1478 and the paper proposing the deprecation P1787R6.

  • Related