C ++ dynamic downcasting for a class template with a template template parameter being a class template or an alias template

I hope the name makes sense. I probably skip the dictionary to express it correctly.

Well, an example is likely to be more understandable.

Problem for me: dynamic downcasting returns 0 at runtime in some of the following cases (written in the comments). I would like to know if this behavior is correct (using C ++ 11), and why, and what I can do to make it work. Templated and A :: A_templated seem to be treated as different classes, although they are defined as identical using the alias "use". The problem does not occur for a simple typedef alias.

template <class T> class Templated {}; class A { public : typedef int A_Type; template <class T> using A_Templated = Templated<T>; }; class Test_base { public : Test_base() {} virtual void foo()=0; }; template <class T> class Test_Type : public Test_base { public : Test_Type() {} void foo() {} }; template < template <class T> class TT > class Test_Templated : public Test_base { public : Test_Templated() {} void foo() {} }; int main() { Test_base* test; test = new Test_Type<int>; std::cout << dynamic_cast< Test_Type<int>* >(test) << std::endl;//-->ok std::cout << dynamic_cast< Test_Type<A::A_Type>* >(test) << std::endl;//-->ok test = new Test_Templated<Templated>; std::cout << dynamic_cast< Test_Templated<Templated>* >(test) << std::endl;//-->ok std::cout << dynamic_cast< Test_Templated<A::A_Templated>* >(test) << std::endl;//--> returns 0 ! test = new Test_Templated<A::A_Templated>; std::cout << dynamic_cast< Test_Templated<A::A_Templated>* >(test) << std::endl;//-->ok std::cout << dynamic_cast< Test_Templated<Templated>* >(test) << std::endl;//--> returns 0 ! } 

I suggest another way to see the problem, this is probably more clear. I came across this while trying to avoid the above example. The following example basically says what Bogdan pointed out. I am very upset that the compiler cannot solve Templated with Templated_alias. I am wondering if there is a compilation option that can sort the type definition of a force through template aliases.

 template <class T> class Templated {}; template <class T> using Templated_alias = Templated<T>; template < template <class T> class TT > class B; template <> class B<Templated> { public : void foo(Templated<int> _arg) {} }; int main() { B<Templated> b1; b1.foo(Templated<int>()); b1.foo(Templated_alias<int>());//compiles => Templated_alias<int> is equivalent to Templated<int> B<Templated_alias> b2;//Compilation error: Implicit instantiation of undefined template B<Templated_alias> //which means: Templated_alias is not equivalent to Templated } 

Thanks to Bogdanโ€™s trick, and after a little nosebleed, I managed to find some solution. The idea is to create a class that is responsible for "filtering" potential template class aliases. It needs one specification for each template class that needs to be "filtered." The main disadvantage of this method is that filtering should therefore be used wherever template classes are used as template parameters in order to be consistent.

 //Classes to be dealt with template <class T> class Templated {}; template <class T> class Templated2 {}; template <class T> using Templated_alias = Templated<T>; class A_base { virtual void foo()=0; }; template <template <class T> class TT> class A : public A_base { void foo() {} }; //Here starts the trick definition template<template<class> class TT1, template<class> class TT2> using is_same_template_t = typename std::is_same<TT1<int>, TT2<int> >::type; //Template Template aliasing template < template <class T> class TT > class TT_aliasing { public : template <class T> using Class_T = TT<T>; }; //Template Template Alias Filtering template < template <class T> class TT, class = std::true_type> class TT_AF { public : template <class T> using Class_T = TT<T>; }; template < template <class T> class TT > class TT_AF<TT, is_same_template_t<TT, Templated> > : public TT_aliasing<Templated> {}; int main() { A_base* a; a = new A< TT_AF<Templated>::Class_T >(); std::cout << dynamic_cast< A< TT_AF<Templated>::Class_T >* >(a) << std::endl; std::cout << dynamic_cast< A< TT_AF<Templated_alias>::Class_T >* >(a) << std::endl; std::cout << dynamic_cast< A< TT_AF<Templated2>::Class_T >* >(a) << std::endl; std::cout << "---------------" << std::endl; a = new A< TT_AF<Templated_alias>::Class_T >(); std::cout << dynamic_cast< A< TT_AF<Templated>::Class_T >* >(a) << std::endl; std::cout << dynamic_cast< A< TT_AF<Templated_alias>::Class_T >* >(a) << std::endl; std::cout << dynamic_cast< A< TT_AF<Templated2>::Class_T >* >(a) << std::endl; std::cout << "---------------" << std::endl; a = new A< TT_AF<Templated2>::Class_T >(); std::cout << dynamic_cast< A< TT_AF<Templated>::Class_T >* >(a) << std::endl; std::cout << dynamic_cast< A< TT_AF<Templated_alias>::Class_T >* >(a) << std::endl; std::cout << dynamic_cast< A< TT_AF<Templated2>::Class_T >* >(a) << std::endl; A< TT_AF<Templated>::Class_T > a1; A< TT_AF<Templated_alias>::Class_T > a2; a1 = a2; A< TT_AF<Templated2>::Class_T > a3; //a1 = a3;//no viable overloaded '=' } 

The conclusion gives:

 0x600000014ba0 0x600000014ba0 0x0 --------------- 0x600000014bb0 0x600000014bb0 0x0 --------------- 0x0 0x0 0x600000014bc0 

After using the above trick. I ran into various problems. One cannot be absolutely sure that this is connected, but it is very likely. The compiler seems to be trying to build the "dynamic table" correctly. I posed this problem in C ++, which can make type_info :: hash_code different for two (supposedly) identical objects. Maybe I'm bad, but at the moment I would not recommend using the trick with Clang 3.1.

+2
source share
1 answer

Clan behavior is correct.

A::A_Type equivalent to int in accordance with [7.1.3p1] in the standard:

[...] As part of its declaration, the name typedef is syntactically equivalent to a keyword and names the type associated with the identifier in the method described in clause 8. Thus, typedef-name is a synonym for another type. The typedef name does not introduce a new type of class declaration (9.1) or enumeration declaration.

A::A_Templated<int> equivalent to Templated<int> according to [14.5.7p2]:

When the template identifier refers to the specialization of the alias template, this is equivalent to the associated type obtained by replacing its template arguments for the template parameters in the template identifier of the alias template type.

However, A::A_Templated not equivalent to Templated , according to [14.5.7p1]:

[...] The nickname template name is the name of the template.

This means that A::A_Templated and Templated are two different templates, so Test_Templated<A::A_Templated> and Test_Templated<Templated> are different specializations of Test_Templated , so casts that return null pointers are correct.

GCC 5.1.0 does not handle this correctly. Clang 3.6.0 and MSVC 14 RC handle it correctly.


All references to the working draft N4431.

Note that there is an active issue with the main working group, Issue 1286, regarding this behavior. The author says that the intention is to introduce a standard wording so that such things work as you expected, i.e. Make the alias template equivalent to the one specified in the type identifier. There is a note dated May 2015, indicating that the problem is attracting attention, but it does not exist yet.


In terms of โ€œgetting it working,โ€ itโ€™s hard to make decisions without knowing what your practical needs are, but I will try to make Test_Templated dependent on the Templated specialization, not the template itself, which declares it as

 template<class T> class Test_Templated : public Test_base { /* ... */ }; 

and use it like

 test = new Test_Templated<Templated<int>>; std::cout << dynamic_cast< Test_Templated<Templated<int>>* >(test) << std::endl; //ok std::cout << dynamic_cast< Test_Templated<A::A_Templated<int>>* >(test) << std::endl; //also ok 

You can wrap this up by adding a level of indirection if that helps anyway:

 template<template<class> class TT, class T> using Make_Test_Templated = Test_Templated<TT<T>>; 

and then use it like this:

 test = new Make_Test_Templated<A::A_Templated, long>; std::cout << dynamic_cast< Make_Test_Templated<A::A_Templated, long>* >(test) << std::endl; //ok std::cout << dynamic_cast< Make_Test_Templated<Templated, long>* >(test) << std::endl; //also ok 

In any case, I think the key is to try to use the fact that the specializations are equivalent.


Well, based on your last update, here is the hack that deals with the problem in the second code example: change the explicit specialization B<Templated> to a partial specialization, which only matches if you specify a template that generates the same specialization as Templated when instantiating with a specific argument (e.g. int for this example).

How is this for a confusing sentence? I'm sorry. Here, what your sample code will be with the above changes:

 #include <iostream> #include <type_traits> template<class> class Templated { }; template<class T> using Templated_alias = Templated<T>; template<class> class Templated2 { }; // Helper trait template<template<class> class TT1, template<class> class TT2> using is_same_template_t = typename std::is_same<TT1<int>, TT2<int>>::type; template<template<class> class, class = std::true_type> class B; template<template<class> class TT> class B<TT, is_same_template_t<TT, Templated>> { public: void foo(Templated<int>) { std::cout << "B<Templated>::foo\n"; } }; int main() { B<Templated> b1; b1.foo(Templated<int>()); b1.foo(Templated_alias<int>()); B<Templated_alias> b2; // Works fine now, and so do the next two lines. b2.foo(Templated<int>()); b2.foo(Templated_alias<int>()); // B<Templated2> b22; // Error trying to instantiate the primary template B. } 

Note that you must ensure that is_same_template_t is only used to check for patterns that can be created using the int argument (change int to whatever you need, of course). If you want to make it more universal, you can also specify the type in which templates should be created in the list of characteristic parameters, for example:

 template<template<class> class TT1, template<class> class TT2, class T> using is_same_template_t = typename std::is_same<TT1<T>, TT2<T>>::type; 

and use it as follows:

 is_same_template_t<TT, Templated, int> 
+3
source

Source: https://habr.com/ru/post/1235033/


All Articles