Variadic 模板仅在前向声明时编译
Variadic template only compiles when forward declared
我有一个继承自所有模板参数的可变参数模板:
template <typename... Ts>
struct derived : Ts...
{
};
我还想有一个工具来表达 "existing derived
with added template arguments" 的类型。我的尝试是:
// Do not ODR-use (goes in namespace impl or similar)!
template<class ... NewInputs, class ... ExistingInputs>
auto addedHelper(const derived<ExistingInputs...>&)
-> derived<ExistingInputs..., NewInputs...>;
template<class ExistingInput, class ... NewInputs>
using Added = decltype(addedHelper<NewInputs...>(std::declval<ExistingInput>()));
举个简单的例子,Added<derived<A, B>, C>
应该是derived<A, B, C>
。我使用辅助函数进行第一个参数包的模板参数推导。
我的问题:出于某种原因,如果 derived
已被前向声明,我可以成功地将其用于不完整的类型,但 不是 如果它已被定义。
为什么这个代码not compile:
#include <utility>
template <typename... Ts>
struct derived : Ts...
{};
template<class ... NewInputs, class ... ExistingInputs>
auto addedHelper(const derived<ExistingInputs...>&)
-> derived<ExistingInputs..., NewInputs...>;
template<class ExistingInput, class ... NewInputs>
using Added = decltype(addedHelper<NewInputs...>(std::declval<ExistingInput>()));
struct A;
struct B;
struct C;
// Goal: This forward declaration should work (with incomplete A, B, C).
auto test(derived<A, B> in) -> Added<decltype(in), C>;
struct A {};
struct B {};
struct C {};
void foo()
{
auto abc = test({});
static_assert(std::is_same_v<decltype(abc), derived<A, B, C>>, "Pass");
}
而此代码 does compile:
#include <utility>
template <typename... Ts>
struct derived;
template<class ... NewInputs, class ... ExistingInputs>
auto addedHelper(const derived<ExistingInputs...>&)
-> derived<ExistingInputs..., NewInputs...>;
template<class ExistingInput, class ... NewInputs>
using Added = decltype(addedHelper<NewInputs...>(std::declval<ExistingInput>()));
struct A;
struct B;
struct C;
// Goal: This forward declaration should work (with incomplete A, B, C).
auto test(derived<A, B> in) -> Added<decltype(in), C>;
template <typename... Ts>
struct derived : Ts...
{};
struct A {};
struct B {};
struct C {};
void foo()
{
auto abc = test({});
static_assert(std::is_same_v<decltype(abc), derived<A, B, C>>, "Pass");
}
为方便起见,这里同时列出两种情况(评论in/out #define FORWARD_DECLARED
):https://godbolt.org/z/7gM52j
我不明白代码怎么可能通过用各自的定义替换前向声明而变得非法(否则稍后会出现)。
hits the nail on the head: the problem here is ADL. It's actually the same problem I ran into .
问题是这样的:我们这里有一个不合格的调用:
template<class ExistingInput, class ... NewInputs>
using Added = decltype(addedHelper<NewInputs...>(std::declval<ExistingInput>()));
// ^^^^^^^^^^^
我们知道它是一个函数模板,因为我们在使用常规查找时发现它,所以我们不必处理整个 "is <
an operator or a template introducer" 问题。但是,因为它是一个不合格的调用,我们 也 必须执行 argument-dependent 查找。
ADL 需要查看所有参数的关联命名空间,这看起来很好 - 我们不需要完整的类型。但是 ADL 还 需要查找 类 中定义的潜在友元函数和函数模板。毕竟,这需要工作:
struct X {
friend void foo(X) { }
};
foo(X{}); // must work, call the hidden friend defined within X
因此,在我们的调用中:
auto test(derived<A, B> in) -> Added<decltype(in), C>;
我们必须实例化derived<A, B>
...但是那个类型继承自两个不完整的类,我们不能这样做。这就是问题所在,这就是我们失败的地方。
这就是前向声明版本有效的原因。 template <typename... T> struct derived;
是不完整的,所以只是试图在它的内部查找友元函数,却一无所获——我们不需要实例化任何其他东西。
同样,derived
已完成但实际上并未派生任何内容的版本也可以使用。
谢天谢地,根据 Evg 的建议,这在这种情况下是可以解决的。拨打合格电话:
template<class ExistingInput, class ... NewInputs>
using Added = decltype(::addedHelper<NewInputs...>(std::declval<ExistingInput>()));
这避免了您甚至不想要的 ADL。最好的情况是,您避免做对您没有好处的事情。糟糕的情况,您的代码无法编译。邪恶的情况,对于某些输入,您不小心调用了完全不同的函数。
或者只使用 Boost.Mp11 的 mp_push_back
我有一个继承自所有模板参数的可变参数模板:
template <typename... Ts>
struct derived : Ts...
{
};
我还想有一个工具来表达 "existing derived
with added template arguments" 的类型。我的尝试是:
// Do not ODR-use (goes in namespace impl or similar)!
template<class ... NewInputs, class ... ExistingInputs>
auto addedHelper(const derived<ExistingInputs...>&)
-> derived<ExistingInputs..., NewInputs...>;
template<class ExistingInput, class ... NewInputs>
using Added = decltype(addedHelper<NewInputs...>(std::declval<ExistingInput>()));
举个简单的例子,Added<derived<A, B>, C>
应该是derived<A, B, C>
。我使用辅助函数进行第一个参数包的模板参数推导。
我的问题:出于某种原因,如果 derived
已被前向声明,我可以成功地将其用于不完整的类型,但 不是 如果它已被定义。
为什么这个代码not compile:
#include <utility>
template <typename... Ts>
struct derived : Ts...
{};
template<class ... NewInputs, class ... ExistingInputs>
auto addedHelper(const derived<ExistingInputs...>&)
-> derived<ExistingInputs..., NewInputs...>;
template<class ExistingInput, class ... NewInputs>
using Added = decltype(addedHelper<NewInputs...>(std::declval<ExistingInput>()));
struct A;
struct B;
struct C;
// Goal: This forward declaration should work (with incomplete A, B, C).
auto test(derived<A, B> in) -> Added<decltype(in), C>;
struct A {};
struct B {};
struct C {};
void foo()
{
auto abc = test({});
static_assert(std::is_same_v<decltype(abc), derived<A, B, C>>, "Pass");
}
而此代码 does compile:
#include <utility>
template <typename... Ts>
struct derived;
template<class ... NewInputs, class ... ExistingInputs>
auto addedHelper(const derived<ExistingInputs...>&)
-> derived<ExistingInputs..., NewInputs...>;
template<class ExistingInput, class ... NewInputs>
using Added = decltype(addedHelper<NewInputs...>(std::declval<ExistingInput>()));
struct A;
struct B;
struct C;
// Goal: This forward declaration should work (with incomplete A, B, C).
auto test(derived<A, B> in) -> Added<decltype(in), C>;
template <typename... Ts>
struct derived : Ts...
{};
struct A {};
struct B {};
struct C {};
void foo()
{
auto abc = test({});
static_assert(std::is_same_v<decltype(abc), derived<A, B, C>>, "Pass");
}
为方便起见,这里同时列出两种情况(评论in/out #define FORWARD_DECLARED
):https://godbolt.org/z/7gM52j
我不明白代码怎么可能通过用各自的定义替换前向声明而变得非法(否则稍后会出现)。
问题是这样的:我们这里有一个不合格的调用:
template<class ExistingInput, class ... NewInputs>
using Added = decltype(addedHelper<NewInputs...>(std::declval<ExistingInput>()));
// ^^^^^^^^^^^
我们知道它是一个函数模板,因为我们在使用常规查找时发现它,所以我们不必处理整个 "is <
an operator or a template introducer" 问题。但是,因为它是一个不合格的调用,我们 也 必须执行 argument-dependent 查找。
ADL 需要查看所有参数的关联命名空间,这看起来很好 - 我们不需要完整的类型。但是 ADL 还 需要查找 类 中定义的潜在友元函数和函数模板。毕竟,这需要工作:
struct X {
friend void foo(X) { }
};
foo(X{}); // must work, call the hidden friend defined within X
因此,在我们的调用中:
auto test(derived<A, B> in) -> Added<decltype(in), C>;
我们必须实例化derived<A, B>
...但是那个类型继承自两个不完整的类,我们不能这样做。这就是问题所在,这就是我们失败的地方。
这就是前向声明版本有效的原因。 template <typename... T> struct derived;
是不完整的,所以只是试图在它的内部查找友元函数,却一无所获——我们不需要实例化任何其他东西。
同样,derived
已完成但实际上并未派生任何内容的版本也可以使用。
谢天谢地,根据 Evg 的建议,这在这种情况下是可以解决的。拨打合格电话:
template<class ExistingInput, class ... NewInputs>
using Added = decltype(::addedHelper<NewInputs...>(std::declval<ExistingInput>()));
这避免了您甚至不想要的 ADL。最好的情况是,您避免做对您没有好处的事情。糟糕的情况,您的代码无法编译。邪恶的情况,对于某些输入,您不小心调用了完全不同的函数。
或者只使用 Boost.Mp11 的 mp_push_back