vendredi 28 août 2015

Failure to instantiate function template involving universal (forward) reference to templated type

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);                                                                                                                       
auto r2 = f (x2);                                                                                                                       
auto r3 = f (x3);                                                                                                                       
auto r4 = f (x4);

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

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).

Why is this not the case? 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?

Aucun commentaire:

Enregistrer un commentaire