C++ dynamic downcasting to class template having template template parameter being a class template or an alias template

狂风中的少年 提交于 2019-11-28 12:52:26

Clang's behaviour is correct.

A::A_Type is equivalent to int according to [7.1.3p1] in the standard:

[...] Within the scope of its declaration, a typedef-name is syntactically equivalent to a keyword and names the type associated with the identifier in the way described in Clause 8. A typedef-name is thus a synonym for another type. A typedef-name does not introduce a new type the way a class declaration (9.1) or enum declaration does.

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

When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template.

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

[...] The name of the alias template is a template-name.

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, thus the casts that return null pointers are correct in doing so.

GCC 5.1.0 doesn't handle this correctly. Clang 3.6.0 and MSVC 14 RC handle it correctly.


All references are to working draft N4431.

Note that there is an active Core Working Group Issue regarding this behaviour - Issue 1286. The author says that the intention is to introduce standard wording to make such cases work the way you expected, that is, make the alias template equivalent to the one referenced in the type-id. There's a note from May 2015 in there, indicating that the issue is receiving attention, but it's not there yet.


In terms of "making it work", it's difficult to give solutions without knowing what your practical needs are, but I'd try to make Test_Templated depend on specializations of Templated, rather than the template itself, that is, declare it like

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 could wrap this by adding a level of indirection, if that helps in any way:

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

Anyway, I think the key is to try to use the fact that the specializations are equivalent.


Alright, based on your latest update, here's a hack addressing the problem in your second code sample: change the explicit specialization B<Templated> to a partial specialization that only matches if given a template that generates the same specialization as Templated when instantiated with a certain argument (let's say int for this example).

How's that for a confusing sentence? Sorry. Here's what your code sample becomes 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 have to make sure is_same_template_t is only used to check templates that can be instantiated with an int argument (change int to whatever you need, of course). If you want to make it more generic, you can also include the type on which the templates need to be instantiated in the trait's parameter list, like this:

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 like this:

is_same_template_t<TT, Templated, int>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!