1

I want to enable user to control "internal type" of base class. This code works fine.

Version 1 (demo)

//library layer
template<class Derived> class BT_trait{
    public: using type=int;  
};
template<class Derived> class BT{
    public: using typee=typename BT_trait<Derived>::type;
    public: typee f(){ return 1;}
};
//user code below
class C;
template<> class BT_trait<C>{
    public: using type=std::string;  //<-- user want to set "internal type"
};
class C : public BT<C> {
    public: typee f(){ return "OK";}
};
int main(){
    C bt;
    std::cout<< bt.f();
}

If I make it a little more complex by adding a template parameter, it will not compilable anymore.
(show below)

Version 2 (demo)

template<class Derived> class BT_trait{
    public: using type=int;  
};
template<class Derived,class Dummy> class BT{
    public: using typee=typename BT_trait<Derived>::type;
    public: typee f(){ return 1;}
};
//user code below
template<class T> class C;
template<class T> class BT_trait<C<T>>{
    public: using type=std::string;  
};
template<class T> class C : public BT<C<T>,T> {
    // public: typename BT<C<T>, T>::typee f(){ return "OK";} //Version #2b 
    public: typee f(){ return "OK";}  //Version #2a
    //^ error: 'typee' does not name a type; did you mean 'wctype'?
};
int main(){
    C<int> bt;
    std::cout<< bt.f();
}

But if I use #2b (dirty) instead of #2a (concise), the above code will work fine.
Why? Is it possible to make #2a work?

According to a quote from Specialization of template function after point of use will break the compilation :-

Section [temp.expl.spec] 14.7.3p6 : If a template, a member template or a member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required.

I doubt that my code is undefined behavior. Correct?
I am very new to template specialization.

cppBeginner
  • 1,114
  • 9
  • 27

2 Answers2

2

Note that typee type depends on template parameters. As an option you can make typee recognizable as is by adding using directive:

using typename BT<C<T>, T>::typee;
public: typee f(){ return "OK";}

online compiler

user7860670
  • 35,849
  • 4
  • 58
  • 84
2

No, your code does not exhibit UB, and will not even when made to compile.

The problem is that once C becomes a template, the two-phase lookup rule starts to apply inside it. Basically, names which do not depend on template parameters are looked up when the template is parsed, at which point in time the argument for T is of course unknown and the compiler thus cannot look into BT<C<T>, T> and find typee defined there. So it fails.

You have to mark typee as a dependent name (because it does depend on T). You have several ways:

  • Qualify it when referring to it:

    public: typename C::typee f(){ return "OK";}
    

    [Live example]

  • Bring it into your class' scope (as also suggested by VTT's answer):

    using typename BT<C<T>, T>::typee;
    public: typee f(){ return "OK";}
    
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455