可变参数模板仅在前向声明时编译

时间:2019-06-21 11:55:07

标签: c++ language-lawyer c++17 variadic-templates forward-declaration

我有一个可变参数模板,该模板继承自所有模板参数:

template <typename... Ts>
struct derived : Ts...
{
};

我还想拥有一种工具来表达“现有的derived并带有添加的模板参数”的类型。我的尝试是:

// 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");
}

为方便起见,以下两种情况都同时出现(对#define FORWARD_DECLARED进行注释/注释):https://godbolt.org/z/7gM52j

我不明白通过用相应的定义替换前向声明(否则稍后会出现),代码怎么可能变得非法。

1 个答案:

答案 0 :(得分:8)

Evg's observation击中了头:这里的问题是ADL。实际上,这是我遇到的with this question相同的问题。

问题是这样的:我们在这里打了一个不合格的电话:

template<class ExistingInput, class ... NewInputs>
using Added = decltype(addedHelper<NewInputs...>(std::declval<ExistingInput>()));
//                     ^^^^^^^^^^^

我们知道它是一个功能模板,因为我们可以通过常规查找找到它,因此我们不必处理整个“是<运算符还是模板引入程序”问题。但是,由于这是一个不合格的调用,因此我们 还必须执行依赖于参数的查找。

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