为什么模板<类型名...>不能通过模板<模板类型名>识别为可实例化<typename>?

Why is a template<typename...> not recognized as instantiatable through template<template<typename> typename>?

本文关键字:lt gt 类型 实例化 typename 为什么 不能 识别      更新时间:2023-10-16

我试图任意"绑定"模板参数,但遇到了优雅问题。

直接切入根本问题,gcc 6.2对以下内容有问题,但从逻辑上讲,我认为它没有问题。。。

template<template<typename, typename> P, typename A, typename B>
struct foo {
static constexpr bool value = P<A, B>::value;
};
template<typename...Ts>
struct bar {
static constexpr bool value = true;
};

给定bar(如foo<bar, void, void>)的foo应导致bar<void, void>的实例化(这是有效的),谁的value成员是true,因此foo<bar, void, void>::value同样是true有效地,这个应该(在我看来)导致实例化结构在概念上类似于…

struct bar<void, void> {
static constexpr bool value = true;
};
struct foo<bar, void, void> {
static constexpr bool value = bar<void, void>::value; //which is true
};

你可以在这里看到这个概念的作用(或者更确切地说是错误)https://godbolt.org/g/lT9umg.

回到开头,首先我尝试了以下。。。

template<typename...>
struct type_list { };
template<template<typename...> typename Tmpl, typename...Ts>
struct bind_template {
template<typename...Us>
using type = Tmpl<Ts..., Us...>;
};
template<template<typename> typename Predicate, typename...Ts>
struct has_matching_type {
private:
template<template<typename> typename, typename, typename=void>
struct helper: std::false_type { };
template<template<typename> typename P, typename U, typename...Us>
struct helper<P, type_list<U, Us...>, typename std::enable_if<P<U>::value>::type>: std::true_type { };
template<template<typename> typename P, typename U, typename...Us>
struct helper<P, type_list<U, Us...>, typename std::enable_if<!P<U>::value>::type>: helper<P, type_list<Us...>> { };
public:
static constexpr bool value = helper<Predicate, type_list<Ts...>>::value;
};
template<typename T, typename...Ts>
using has_type = has_matching_type<bind_template<std::is_same, T>::template type, Ts...>;

稍后我可能会尝试通过has_type<T, Ts...>实例化,例如。。。

cout << has_type<long, int, bool, long, float>::value << endl;

然而,正如我所指出的,gcc 6.2.0抱怨,因为它似乎没有意识到,一旦解决了问题,模板实例化在实用上是等效的。

只需知道模板参数的数量并专门针对该确切数量即可解决问题。如果我专注于bound_template,并牢记std::is_same<LHS, RHS>。。。

template<template<typename, typename> typename Tmpl, typename T>
struct bind_template<Tmpl, T> {
template<typename U>
using type = Tmpl<T, U>;
};

我们突然编译并评估编译时间没有问题,因为gcc认为CCD_ 12正好取一个类型参数。

显然,无论编译器如何,将这一概念抽象出来以允许任何模板参数,如积分常数而不仅仅是类型,都是一个基本问题。只是简单地关注一下类型,我的问题是多方面的:

  1. 我是不是在概念上遗漏了什么,而编译器实际上正在做对我来说应该显而易见的事情
  2. 如果不是,这是否违反了C++11标准,不是由标准指导的,还是编译器依赖
  3. 不管我的前两个问题的答案是什么,有没有什么优雅的方法可以绕过这个问题

在功能上,真正的问题(特别是如果这是C++11中不可避免的问题)是…

有没有什么优雅的方法可以抽象地绑定模板,而不必专门针对每种情况(这里最简单的是n类型的数量)

只要能得到问题1或3的答案就太好了。问题3是最重要的,因为归根结底,重要的是什么有效。

显然,我可以专门化(如上所示)。然而,一个大问题是,即使以下内容似乎也不起作用(至少根据这个在线编译器的说法)。。。

template<template<typename...> class Tmpl, typename... Ts>
struct bind_helper {
template<typename... Us>
struct type: Tmpl<Ts..., Us...> { };
template<typename A>
struct type<A>: Tmpl<Ts..., A> { };
template<typename A, typename B>
struct type<A, B>: Tmpl<Ts..., A, B> { };
template<typename A, typename B, typename C>
struct type<A, B, C>: Tmpl<Ts..., A, B, C> { };
};

这意味着,我不仅必须生成一堆参数,而且还必须通过完全的bind_template专门化来匹配外部参数。这很快就变成了(实际上)二项式问题。

进一步扩展这个概念(但仍然保留类型),我计划下一步实现"占位符",就像std::bind使用占位符一样(这会非常优雅,因为我只是剥离并重新加入索引处的列表)。显然,如果没有更抽象的方法,这太混乱了。

这在C++17中是固定的。具体而言,根据14.3.3"模板-模板论据"第3段:

template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };
template<class ... Types> class C { /* ... */ };
template<auto n> class D { /* ... */ };
template<template<class> class P> class X { /* ... */ };
template<template<class ...> class Q> class Y { /* ... */ };
template<template<int> class R> class Z { /* ... */ };
X<A> xa;            // OK
X<B> xb;            // OK
X<C> xc;            // OK
Y<A> ya;            // OK
Y<B> yb;            // OK
Y<C> yc;            // OK
Z<D> zd;            // OK

这里的相关示例是X<C>。这在带有标志-std=c++1z的g++7中起作用。

C++14指定上面的例子是格式错误的:

template<class T> class A { /∗ ... ∗/ };
template<class T, class U = T> class B { /∗ ... ∗/ };
template <class ... Types> class C { /∗ ... ∗/ };
template<template<class> class P> class X { /∗ ... ∗/ };
template<template<class ...> class Q> class Y { /∗ ... ∗/ };
X<A> xa; // OK
X<B> xb; // ill-formed: default arguments for the parameters of a template argument are ignored
X<C> xc; // ill-formed: a template parameter pack does not match a template parameter

这一变化发生在2016年末的论文DR中:模板-模板参数的匹配不包括兼容的模板。此更改从11月开始应用于此提交。委员会自1999年或以前就知道这个问题。

在C++17之前,由于jbapple的回答中提到的一个不幸的语言缺陷,此代码的格式是错误的。

一个符合C++11的解决方案是从到处使用元函数切换到到处使用元功能类。根据Boost.MPL的定义,元函数类将是一个具有名为apply的模板别名的类型。我们可以这样调用:

template <class MFC, class... Ts>
using apply_t = typename MFC::template apply<Ts...>;

我们可以通过将模板提升到元函数类中

template <template <typename...> class Z>
struct quote {
template <class... Args>
using apply = Z<Args...>;
};

然后,让我们重写bind_template,使用元函数类而不是模板:

template <class MFC, class... Ts>
struct bind_template {
template <class... Us>
using apply = apply_t<MFC, Ts..., Us...>;
};

然后重写has_matching_type以采用元函数类而不是模板:

template<class Predicate, class... Ts>
struct has_matching_type {
private:
template<class>
struct helper: std::false_type { };
template<typename U, typename...Us>
struct helper<type_list<U, Us...>>
: std::conditional<
apply_t<Predicate, U>::value,
std::true_type,
helper<type_list<Us...>>
>::type
{ };
public:
static constexpr bool value = helper<type_list<Ts...>>::value;
};
template<class T, class... Ts>
using has_type = has_matching_type<bind_template<quote<std::is_same>, T>, Ts...>;

现在,您的初始has_type<long, int, bool, long, float>::valuetrue,即使在C++11中也是如此。