由于对模板类型的通用(转发)引用而无法实例化函数模板 [英] A failure to instantiate function templates due to universal (forward) reference to a templated type

查看:139
本文介绍了由于对模板类型的通用(转发)引用而无法实例化函数模板的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

通用参考(即前进引用, c ++ 标准名称)和完美转发 c ++ 11 c ++ 14 ,以及具有许多重要的优点;请参阅此处此处



在Scott Meyers的文章(链接),它被表示为经验法则:


如果变量或参数声明为 T&&




示例1

$

b
$ b

事实上,使用clang ++我们看到下面的代码片段将成功编译 -std = c ++ 14

  #include< utility> 

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

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

//所有调用`f`会导致成功
//绑定T&到所需类型
auto r1 = f(x1); //各种左值正常,如预期
auto r2 = f(x2); // ...
auto r3 = f(x3);
auto r4 = f(x4);
auto r5 = f(int()); // rvalues okay,as expected

给定通用引用(前向引用)和类型推导例如,请参阅此解释),可以清楚了解上述工作原因。虽然从同样的解释,它不是很清楚为什么下面的工作不好。



(失败)示例2



此问题解决了相同的问题。然而,所提供的答案并不能解释为什么模板类型没有被归类为推导。



我要显示(似乎)满足上述要求作者:Meyers。但是,以下代码剪切失败进行编译,产生错误(其中每次调用 f ):


test.cpp:23:11:错误:没有匹配函数调用'f'



auto r1 = f(x1);



test.cpp:5:16:note:候选函数[with T = foo,A = int] not
:没有从'struct foo< int>'to'foo< int>&&'
第一个参数



decltype(auto)f(T & b $ b



  #include< utility> 

//
//看起来**模板化类型T< A>应该
//与
//通用引用相同的裸体类型T,但是不是这样。
//
template< template< typename>类型名称T,类型名称A>
decltype(auto)f(T< A& t)
{
return std :: forward< T& (t)。
}

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

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

//所有调用`f` **失败**编译到
//到**不成功**绑定T&&到所需类型
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 < c c c / c> T< A>&



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

让我强调以下几点:示例2 中的代码无法编译事实上 struct foo<> 是一个模板类型。通过将 f 的参数声明为模板类型,该失败似乎仅导致



请考虑以前的代码的修订版,现在 编译:

  #include< utility> 

//
//如果我们像以前一样重新声明`f',其中`T`不再是
//模板类型参数,我们的代码再一次工作。
//
template< typename T>
decltype(auto)f(T& t)
{
return std :: forward< T& (t)。
}

//
//注意,`struct foo<>`是** still **模板类型。
//
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>常数& x4 = x2;

//所有调用`f`(再一次)导致
//成功绑定T&到所需类型
auto r1 = f(x1);
auto r2 = f(x2);
auto r3 = f(x3);
auto r4 = f(x4);

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



问题:



为什么第二个例子不能按预期工作?有没有技术来克服这个问题与 c ++ 11/14 中的模板类型?有没有成功使用 c ++ 的模板类型的向前引用的着名代码库(在野外)?

f 和一些左值时:



<$> p $ p> int a = 42;
f(a);

然后 f 一个左值。这是 f 的第一个参数是(lvalue)引用类型,或者它根本不是引用时的情况:

  auto f(int&); 
auto f(int); //假设一个工作副本构造函数

这个参数是右值引用:

  auto f(int&& // error 

现在,当你定义一个转发引用作为第一个参数的函数时,第一个和第三个例子...

  template< typename T> 
auto f(T&&); //只显示声明

...你实际上用一个左值,模板类型扣除将 T 转换为(lvalue)引用(这种情况可以在我提供的示例代码中看到):


$ b b

  auto f(int&&&&& amp;); //认为它像这样

当然,上面涉及的引用过多。所以C ++有 折叠规则 ,其实很简单:




  • & 成为 T&

  • &&&&&< / code>变成 T&

  • & & 成为 T&

  • &&&&&< / code>成为 T&&&< / code>



由于第二条规则, f 的第一个参数的有效类型是一个左值引用,因此您可以将其左值绑定到它。



现在当你定义一个函数 g like ...

  template< template< class>类T,类型名A> 
auto g(T< A&&&&&&

那么无论如何,模板参数扣除必须 $ c> T 插入模板。毕竟,您在将模板参数声明为 template< class> class 而不是 typename
(这是一个重要的区别, foo 在你的例子中是一个类型,它是一个模板...你可以看到作为类型级别函数,但回到主题)



现在, T 是某种模板。您不能具有对模板的引用。
引用(类型)是从(可能不完整的)类型构建的。因此,不管什么, T (这是一种类型,但不是可以推导出的模板参数) (lvalue)引用,这意味着 T < &&&&< / code>不需要任何折叠并保持原样:右值引用。当然,你不能将一个左值绑定到右值引用。



但是如果你传递一个右值,那么甚至 g

<$ p <$ p <$ p <$ p <$ p <$ p <$ p <$ p> > 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<< 参考<< endl
return std :: forward< T>(t);
}
template<
template< class>类T,
类型名A>
decltype(auto)g(T< A& t){
return std :: forward< T&
}
int main(int,char **){
thing< int> it {};

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

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

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

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

return 0;
}

Live here



关于如何克服这个问题:你不能。至少不是你想要的方式,因为自然的解决方案是你提供的第三个例子:因为你不知道传递的类型(它是一个左值引用,一个右值引用或引用所有?)你必须保持它作为泛型 T 。你当然可以提供重载,但是这会以某种方式打败完美的转发的目的,我猜。






你实际上可以克服这一点,使用一些traits类:

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

然后,您可以提取模板和类型的模板实例化的函数内: / p>

 模板< typename Y> 
decltype(auto)g(Y& t){
//需要一些手动工作,但是...
使用trait = traits< typename std :: remove_reference& :: 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







[... ]可以 解释为什么它不是通用引用?它的危险或陷阱是什么,或者它难以实现?我真诚地感兴趣。


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



将这个转发引用的一个严重缺陷是,你不能再表达 T< A>&& :从参数 A 的模板 T 构建的某种类型的值。 b $ b

Universal references (i.e. "forward references", the c++ standard name) and perfect forwarding in c++11, c++14, and beyond have many important advantages; see here, and here.

In Scott Meyers' article referenced above (link), it is stated as a rule of thumb that:

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

Example 1

Indeed, using clang++ we see that the following code snippet will successfully compile with -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

Given any description of universal references (forward references) and type deduction (see, for instance, this explanation) it is clear why the above works. Although, from the same explanation, it is not abundantly clear why the below fails to work as well.

(failed) Example 2

This question addresses the same issue. The provided answers do not, however, explain why templated types are not categorized as being "deduced".

What I am about to show (seemingly) satisfies the requirement stated above by Meyers. However, the following code snipped fails to compile, producing the error (among others for each call to 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

In context, since the type T<A> of f's parameter is deduced, surely the parameter declaration T<A>&& t would behave as a universal reference (forward reference).

Example 3 (for clarity in describing the problem at hand)

Let me stress the following: the failure of the code in Example 2 to compile is not due to the fact that struct foo<> is a templated type. The failure seems to be cause only by the declaration of f's parameter as a templated type.

Consider the following revision to the previous code, which now does compile:

#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);

It is astonishing to me that this simple change completely alters the behaviour of the type deduction for the template function f's type parameter.

Questions:

Why does the second example not work as expected? Are there techniques to overcome this problem with templated types in c++11/14? Are there well known, extant codebases (in the wild) making successful use of c++'s forward references with templated types?

解决方案

When you call some function f with some lvalue:

int a = 42;
f(a);

Then f must be able to accept such an lvalue. This is the case when the first parameter of f is a (lvalue) reference type, or when it's not a reference at all:

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

This won't work when the parameter is a rvalue reference:

auto f(int &&); // error

Now, when you define a function with a forwarding reference as first parameter as you did in the first and third example ...

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

... and you actually call this function with an lvalue, template type deduction turns T into an (lvalue) reference (that this happens can be seen in the example code I provide in a moment):

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

Surely, there are too much references involved above. So C++ has collapsing rules, which are actually quite simple:

  • T& & becomes T&
  • T& && becomes T&
  • T&& & becomes T&
  • T&& && becomes T&&

Thanks to the second rule, the "effective" type of the first parameter of f is a lvalue reference, so you can bind your lvalue to it.

Now when you define a function g like ...

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

Then no matter what, template parameter deduction must turn the T into a template, not a type. After all, you specified exactly that when declaring the template parameter as template<class> class instead of typename. (This is an important difference, foo in your example is not a type, it's a template ... which you can see as type level function, but back to the topic)

Now, T is some kind of template. You cannot have a reference to a template. A reference (type) is built from a (possibly incomplete) type. So no matter what, T<A> (which is a type, but not a template parameter which could be deduced) won't turn into an (lvalue) reference, which means T<A> && doesn't need any collapsing and stays what it is: An rvalue reference. And of course, you cannot bind an lvalue to an rvalue reference.

But if you pass it an rvalue, then even g will work.

All of the above can be seen in the following example:

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)

Concerning how one could "overcome" this: You cannot. At least not the way you seem to want it to, because the natural solution is the third example you provide: Since you don't know the type passed (is it an lvalue reference, a rvalue reference or a reference at all?) you must keep it as generic as T. You could of course provide overloads, but that would somehow defeat the purpose of having perfect forwarding, I guess.


Hm, turns out you actually can overcome this, using some traits class:

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>;
};

You can then extract both the template and the type the template was instantiated with inside of the function:

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>&& isn't an universal reference because T<A> isn't a template parameter. It's (after deduction of both T and A) a simple (fixed / non generic) type.

A serious pitfall of making this a forwarding reference would be that you could no longer express the current meaning of T<A>&&: An rvalue reference to some type built from the template T with parameter A.

这篇关于由于对模板类型的通用(转发)引用而无法实例化函数模板的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆