Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using reference as template type

Tags:

c++

In the following example, Foo is not doing what is intended, but I can't figure out why this is allowed to compile.

#include <string>
#include <iostream>

typedef std::string& T;

T Foo(int & i)
{
    return T(i);
}

int main()
{
    int a = 1;
    std::string & s = Foo(a);
}

I discovered this with templates, but the typedef shows that its unrelated to templates. Needless to say, s is not a valid string here. I would think that constructing the value in the return of Foo would produce a compile error.

What am I missing here?

like image 580
JaredC Avatar asked Dec 17 '12 00:12

JaredC


People also ask

How do you use template arguments in C++?

A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)

Which is correct example of template parameters?

For example, given a specialization Stack<int>, “int” is a template argument. Instantiation: This is when the compiler generates a regular class, method, or function by substituting each of the template's parameters with a concrete type.

What is a non type template parameter?

A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument. A non-type parameter can be any of the following types: An integral type. An enumeration type. A pointer or reference to a class object.

What is meant by template parameter?

A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.


2 Answers

First of all, it worth nothing that the problem actually has no relationship to templates because this code compiles a well:

typedef std::string& T;
T Foo(int& i) {
    return T(i);
}

The reason I think this compiles is that the return statement is equivalent to

return reinterpret_cast<T>(i);

in case T happens to be a reference members. ... and this, of course, compiles: You promised you knew what you were doing and asked the compiler kindly to believe you.

OK, found it at 5.2.3 [expr.type.conv] paragraph 1:

... If the expression list is a single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression (5.4). ...

... and 5.4 [expr.cast] paragraph 4:

The conversions performed by [other forms of casts] a reinterpret_cast (5.2.10) [...] can be performed using the cast notation of explicit type conversion. [...]

(the elisions cover cases involving user defined type, built-in type conversions, const conversions, etc.)

like image 146
Dietmar Kühl Avatar answered Oct 12 '22 09:10

Dietmar Kühl


This has nothing to do with templates, you get the same result if T is just a typedef for std::string& rather than a deduced template parameter:

#include <string>

typedef std::string& T;

T Foo(int & i)
{
    return T(i);
}

int main()
{
    int a = 1;
    std::string & s = Foo(a);
}

Dietmar's answer made me realise this can be further simplified to:

#include <string>

typedef std::string& T;

int main()
{
    int a = 1;
    std::string & s = T(a);
}

where T(a) is the same as the cast (T)a i.e. (std::string&)a which (according to the rules of 5.4 [expr.cast]) will do a const_cast if that's valid (which it isn't) or a static_cast if that's valid (which it isn't) or a static_cast followed by a const_cast if that's valid (which it isn't) or a reinterpret_cast if that's valid (which it is) or a reinterpret_cast followed by a const_cast if that's valid, otherwise the expression is ill-formed.

So as Dietmar said, it's the same as doing a reinterpret_cast, i.e.

std::string & s = reinterpret_cast<std::string&>(a);

I find it quite surprising that the original code compiles, but as it's the same as that line above, it's allowed to compile. Using the result of the cast is undefined behaviour though.

To avoid the surprise where T(a) is equivalent to a cast, use the new C++11 uniform initialization syntax, T{a}, which is always an initialization, not a cast expression.

Great question, investigating and answering it showed me a new gotcha I wasn't previously aware of, thanks to JaredC and Dietmar for the new piece of knowledge!

like image 27
Jonathan Wakely Avatar answered Oct 12 '22 09:10

Jonathan Wakely