Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I write a decltype expression using a function that expects a non-const reference?

Consider:

int convert_it(std::string& x)
{
    return 5;
}

void takes_int_ref(int& i)
{
}

I want to write a function which only exists if convert_it can be applied and the result passed into takes_int_ref. That is, the function body is:

template <typename A>
void doit(A& a) 
{
    int i = convert_it(a);
    takes_int_ref(i);
}

However, if I do:

template <typename A>
auto doit(A& a) -> decltype(takes_int_ref(convert_it(a)), void())

it doesn't work because invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'.

I thought of the following solution, which works:

template <typename T>
T& gimme_ref(T t) { throw std::runtime_error("No"); return t; }

template <typename A>
auto doit(A& a) -> decltype(takes_int_ref(gimme_ref(convert_it(a))), void()) 

However, it seems hackish and the decltype no longer reflects what the function body does. In essence the problem seems to be that decltype only takes an expression, while two statements are required in the function body here.

What would be the right approach to take here?

like image 760
Claudiu Avatar asked Jun 11 '15 15:06

Claudiu


People also ask

What is the decltype of a function?

The decltype type specifier yields the type of a specified expression. The decltype type specifier, together with the auto keyword, is useful primarily to developers who write template libraries. Use auto and decltype to declare a function template whose return type depends on the types of its template arguments.

What is the difference between auto and decltype in C++?

'auto' lets you declare a variable with a particular type whereas decltype lets you extract the type from the variable so decltype is sort of an operator that evaluates the type of passed expression.

What does decltype return?

decltype returnsIf what we pass to decltype is the name of a variable (e.g. decltype(x) above) or function or denotes a member of an object ( decltype x.i ), then the result is the type of whatever this refers to. As the example of decltype(y) above shows, this includes reference, const and volatile specifiers.


2 Answers

Use std::declval:

template <typename A>
auto doit(A& a) -> decltype(
    takes_int_ref(std::declval<
       decltype(convert_it(std::declval<A&>()))
       &>()), void())
{ .. }

std::declval<A&>() gives you an expresion of type A&. convert_it(A&) will either be valid or not - if it's invalid, you fail there. If it's valid, say it has type T. Then, you try to call takes_int_ref with a T&, so to see if that's valid. If it is, you'll get to the void. If it's not, substitution failure.

like image 186
Barry Avatar answered Nov 02 '22 22:11

Barry


For the record, after seeing the std::declval solution, my perception of what is hackish changed, and I ended up going with this:

template <typename T> T& lref_of(T&&);

template <typename A>
auto doit(A& a) -> decltype(takes_int_ref(lref_of(convert_it(a))), void()) 
{ .. }

Since I'm starting from an expression and not a type, it's cleaner to do lref_of(convert_it(a)) than std::declval<decltype(convert_it(a))&>(). Plus, not defining lref_of gives a compile-time error if it's used in any code which is better than defining it to simply throw an exception at runtime.

like image 42
Claudiu Avatar answered Nov 02 '22 23:11

Claudiu