c++ - 由于对模板化类型的通用(前向)引用而无法实例化函数模板

标签 c++ templates c++11 c++14 perfect-forwarding

Universal references (即“前向引用”,c++ 标准名称)和 c++11 中的完美转发, c++14 , beyond 有很多重要的优势;见here , 和 here .

在上面引用的 Scott Meyers 的文章 ( link ) 中,根据经验规定:

If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.

例子1

确实,使用 clang++ 我们看到以下代码片段将成功编译 -std=c++14 :

#include <utility>

template <typename T>
decltype(auto) f(T && t)
{
    return std::forward<T>(t);
}

int        x1 = 1;
int const  x2 = 1;
int&       x3 = x1;
int const& x4 = x2;

// all calls to `f` result in a successful
// binding of T&& to the required types
auto r1 = f (x1);    // various lvalues okay, as expected
auto r2 = f (x2);    // ...
auto r3 = f (x3);
auto r4 = f (x4);
auto r5 = f (int()); // rvalues okay, as expected

鉴于对通用引用(前向引用)和类型推导(例如,参见 this explanation )的任何描述,很明显为什么上面的方法有效。虽然,从同样的解释来看,为什么下面的方法也不起作用还不是很清楚。

(失败)示例 2

This question解决了同样的问题。然而,所提供的答案并没有解释为什么模板化类型没有被归类为“推导”。

我要展示的(表面上)满足迈耶斯的上述要求。但是,以下代码截断了编译失败,产生了错误(以及对 f 的每次调用):

test.cpp:23:11: error: no matching function for call to 'f'

auto r1 = f (x1);

test.cpp:5:16: note: candidate function [with T = foo, A = int] not viable: no known conversion from 'struct foo< int >' to 'foo< int > &&' for 1st argument

decltype(auto) f (T< A > && t)

#include <utility>

//
// It **seems** that the templated type T<A> should
// behave the same as an bare type T with respect to
// universal references, but this is not the case.
//
template <template <typename> typename T, typename A>
decltype(auto) f (T<A> && t)
{
    return std::forward<T<A>> (t);
}

template <typename A>
struct foo
{
    A bar;
};

struct foo<int>        x1 { .bar = 1 };
struct foo<int> const  x2 { .bar = 1 };
struct foo<int> &      x3 = x1;
struct foo<int> const& x4 = x2;

// all calls to `f` **fail** to compile due
// to **unsuccessful** binding of T&& to the required types
auto r1 = f (x1);
auto r2 = f (x2);
auto r3 = f (x3);
auto r4 = f (x4);
auto r5 = f (foo<int> {1}); // only rvalue works

在上下文中,由于类型 T<A>f的参数推导出来的,肯定是参数声明T<A>&& t将充当通用引用(前向引用)。

示例 3(为了清楚地描述手头的问题)

让我强调以下几点:Example 2 中的代码失败编译是不是,因为 struct foo<>是模板化类型。失败似乎f 的声明引起的参数作为模板化类型。

考虑对之前代码的以下修改,现在可以编译:

#include <utility>

//
// If we re-declare `f` as before, where `T` is no longer a
// templated type parameter, our code works once more.
//
template <typename T>
decltype(auto) f (T && t)
{
    return std::forward<T> (t);
}

//
// Notice, `struct foo<>` is **still** a templated type.
//
template <typename A>
struct foo
{
    A bar;
};

struct foo<int>        x1 { .bar = 1 };
struct foo<int> const  x2 { .bar = 1 };
struct foo<int> &      x3 = x1;
struct foo<int> const& x4 = x2;

// all calls to `f` (again) result in
// a successful binding of T&& to the required types
auto r1 = f (x1);
auto r2 = f (x2);
auto r3 = f (x3);
auto r4 = f (x4);

令我惊讶的是,这个简单的更改完全改变了模板函数的类型推导行为 f的类型参数。

问题:

为什么第二个例子没有按预期工作? c++11/14 中是否有技术可以解决模板化类型的这个问题? ?是否有众所周知的现存代码库(在野外)成功使用 c++具有模板化类型的前向引用?

最佳答案

当你调用某个函数时 f有一些左值:

int a = 42;
f(a);

然后f必须能够接受这样的左值。 f的第一个参数就是这种情况是(左值)引用类型,或者当它根本不是引用时:

auto f(int &);
auto f(int); // assuming a working copy constructor

当参数是右值引用时,这不会工作:

auto f(int &&); // error

现在,当您像在第一个和第三个示例中所做的那样定义一个将转发引用作为第一个参数的函数时...

template<typename T>
auto f(T&&); // Showing only declaration

...你实际上用一个左值调用这个函数,模板类型推导变成了T进入一个(左值)引用(这种情况可以在我稍后提供的示例代码中看到):

auto f(int & &&); // Think of it like that

当然,上面涉及的引用太多了。所以 C++ 有 collapsing rules ,其实很简单:

  • T& &变成 T&
  • T& &&变成 T&
  • T&& &变成 T&
  • T&& &&变成 T&&

感谢第二条规则,f 的第一个参数的“有效”类型是左值引用,因此您可以将左值绑定(bind)到它。

现在当你定义一个函数时 g喜欢...

template<template<class> class T, typename A>
auto g(T<A>&&);

那么无论如何,模板参数推导必须T到一个模板不是一个类型。毕竟,在将模板参数声明为 template<class> class 时,您已准确指定了它而不是 typename . (这是一个重要的区别,foo 在您的示例中不是类型,它是一个模板......您可以将其视为类型级别的函数,但回到主题)

现在,T是某种模板。您不能引用模板。 引用(类型)是从(可能不完整的)类型 构建的。所以无论如何,T<A> (这是一种类型,但不是可以推导的模板参数)不会变成(左值)引用,这意味着 T<A> &&不需要任何折叠并保持原样:右值引用。当然,您不能将左值绑定(bind)到右值引用。

但是如果你给它传递一个右值,那么即使是g将工作。

以上所有内容都可以在以下示例中看到:

template<typename X>
struct thing {
};
template<typename T>
decltype (auto) f(T&& t) {
 if (std::is_same<typename std::remove_reference<T>::type, T>::value) {
  cout << "not ";
 }
 cout << "a reference" << endl;
 return std::forward<T>(t);
}
template<
 template<class> class T,
 typename A>
decltype (auto) g(T<A>&& t) {
 return std::forward<T<A>>(t);
}
int main(int, char**) {
 thing<int> it {};

 f(thing<int> {}); // "not a reference"

 f(it);            // "a reference"
 // T = thing<int> &
 // T&& = thing<int>& && = thing<int>&

 g(thing<int> {}); // works

 //g(it);
 // T = thing
 // A = int
 // T<A>&& = thing<int>&&

 return 0;
}

( Live here )

关于如何“克服”这一点:你不能。至少不是你想要的方式,因为自然的解决方案是你提供的第三个例子:因为你不知道传递的类型(它是左值引用,右值引用还是在全部?)你必须保持它像T一样通用.您当然可以提供过载,但我猜这会以某种方式破坏完美转发的目的。


嗯,事实证明你实际上可以克服这个问题,使用一些特征类:

template<typename> struct traits {};
template<
 template<class>class T,
 typename A>
struct traits<T<A>> {
 using param = A;
 template<typename X>
 using templ = T<X>;
};

然后您可以提取模板和模板在函数内部实例化的类型:

template<typename Y>
decltype (auto) g(Y&& t) {
 // Needs some manual work, but well ...
 using trait = traits<typename std::remove_reference<Y>::type>;
 using A = typename trait::param;
 using T = trait::template templ
 // using it
 T<A> copy{t};
 A data;
 return std::forward<Y>(t);
}

( Live here )


[...] can you explain why it is not an universal reference? what would the danger or the pitfall of it be, or is it too difficult to implement? I am sincerely interested.

T<A>&&不是通用引用,因为 T<A>不是模板参数。它是(在扣除 TA 之后)一个简单的(固定/非通用)类型。

将其设为转发引用的一个严重缺陷是您无法再表达 T<A>&& 的当前含义。 : 对从模板构建的某种类型的右值引用 T带参数 A .

关于c++ - 由于对模板化类型的通用(前向)引用而无法实例化函数模板,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32282705/

相关文章:

c++ - 如何在基类中存储和执行派生类成员函数

c++ - 任意功能的定时器

可变序列 : How to create similar code block for each argument/type? 的 C++ 模板和代码生成

c++ - 隐藏内存分配?

c++ - 如何在另一个构造函数中调用对象构造函数?

c++ - 在c++中实现复数幂函数?

c++ - 将一个模块的所有代码放在一个接口(interface)后面。好主意与否?

C++11 外部模板 : where do we actually need them?

c++ - 我们可以使用 lambda 创建 unique_ptr 吗?

c++ - 如何使用运算符重载来简化两个分数的加法?