Template neglects const (why?) - c++

Does somebody know, why this compiles??
template< typename TBufferTypeFront, typename TBufferTypeBack = TBufferTypeFront>
class FrontBackBuffer{
public:
FrontBackBuffer(
const TBufferTypeFront front,
const TBufferTypeBack back): ////const reference assigned to reference???
m_Front(front),
m_Back(back)
{
};
~FrontBackBuffer()
{};
TBufferTypeFront m_Front; ///< The front buffer
TBufferTypeBack m_Back; ///< The back buffer
};
int main(){
int b;
int a;
FrontBackBuffer<int&,int&> buffer(a,b); //
buffer.m_Back = 33;
buffer.m_Front = 55;
}
I compile with GCC 4.4. Why does it even let me compile this? Shouldn't there be an error that I cannot assign a const reference to a non-const reference?

The thing is that if type T is int&, then the type const T is not const int&, but int & const. The illegal top-level const on a reference is ignored in template substitutions and typedef results.
If, on the other hand T is const int, then T& is const int&

When TypeBufferFront is int&, const TBufferTypeFront is equivalent to int& const, where the const is ignored during template substitution, since all references are constant, even if what they refer to is not.
So, when instantiated with int&, your constructor is effectively FrontBackBuffer(int&, int&), which works as given.
This is an example of why many people will use T const instead of const T, to make it clearer how the substitution occurs, as well as allow them to read the cv-qualifiers from right to left.

For the code to do what you want it to do, it would have to read:
FrontBackBuffer(
typename std::remove_reference<TBufferTypeFront>::type const& m_front,
typename std::remove_reference<TBufferTypeBack>::type const& m_back): ////const reference assigned to reference???
m_Front(m_front),
m_Back(m_back)
{
};
which has the added "feature" that it turns other types into const references when used to construct FrontBackBuffer.
Now this isn't perfect. This prevents temporary arguments to FrontBackBuffer from being moved, and passes even small cheap to copy types (like char) by reference instead of by value. There are standard C++0x techniques to do this that are a bit awkward to write if you care.

FrontBackBuffer::m_Front is of type TBufferTypeFront which translates to int& in your template instantiation. There is nothing wrong with assigning to an int&.

Related

How does C++ function template specialization work?

I am reading C++ Primer (5th Edition), 16.5, Defining a Function Template Specialization, and confused about the example given by author, let's see the following template function:
template <typename T> int compare(const T&, const T&);
and its specialization version:
template <>
int compare(const char* const &p1, const char* const &p2)
{
return strcmp(p1, p2);
}
Type of T will be const char *, but I don't think the function could be the specialization version of the template function, because I think const char* const &p1 can only be the specialization of T const & but const T &, I know I'm wrong, but I want to know why I am wrong.
Edit:
One thing to emphasize, if I invoke compare("hi", "mom"), it won't compile, that is to say, template <typename T> int compare(const T&, const T&) can not be initialized to int compare(const char* const &p1, const char* const &p2), I know T will be initialized with char[3] or char[4], but now that this won't compile, why won't compiler ignore this kind of initialization but choose one that will compile?
In C++, const T and T const mean exactly the same thing. Therefore const T & and T const & mean exactly the same thing.
It couldn't really be any other way, because references can never be changed to refer to something else (they are not "reseatable"). If you read T const & as "a reference which cannot be changed to refer to a different T", that's incorrect. It is "a reference to a T, which cannot be used to modify that T (and as with all references, cannot be changed to refer to a different T)."
const can be either before or after a type, except for a pointer type, where it must be on the right side.
Now the tricky part here is, that T in your example is set to const char*, which is a pointer type (pointer to a const char). The template says that T has to be const, and since it's a pointer type the const in the specialization has to be put after the type, hence you get const char* const.
It becomes a bit more clear when reading it aloud from right to left:
"a const pointer to a char that is const"
//Edit:
Why can't you call compare("hi", "mom");? Because those char arrays are treated as different types by your compiler (char[3] and char[4]), but the template specifies both parameters to be the same type.
This will match the template, however it will not match your specialization (as T is now char[2]):
compare("a", "b");
This works and uses your specialized method:
const char * hi = "hi";
const char * mom = "mom";
compare(hi, mom);
//Edit2:
"I know T will be initialized with char[3] or char[4], [...] why won't compiler ignore this kind of initialization but choose one that will compile?"
Because C++ is a strongly typed language. The compiler doesn't do guess work for you, it takes the types at face value. If they don't match, they don't match. It's your job as developer to do it right.

Why type deduced for T is const int in void func(T const &t)

I am reading about type deduction in templates, and here is a question that bothers me
template<typename T>
void funt(T const &t);
int x = 10;
func(x);
T will be deduced to be const int, and the type of t will be int const &
I understand the reasons for why t has to be int const &, so that xpassed to the function would remain the same, because function accepts const T&.
But I don't see the reason why T has to be also const. Seems to me that deducing T to be int would not break anything within this piece of code?
Like in another example:
template<typename T>
void funt(T const &t);
int const x = 10;
func(x);
Here T is deduced to just int, and const of x is omitted
Or am I missing something here?
The type deduction rules are not very straightforward. But as a general rule of thumb, there are 3 main type-deduction cases:
f(/*const volatile*/ T& arg) - In this instance, the compiler performs something similar to "pattern matching" on the argument to deduce the type T. In your second case, it perfectly matches the type of your argument, i.e. int const, with T const, hence T is deduced as int. In case the argument lacks CV-qualifiers, like in your first example, then the compiler does not consider them in the pattern matching. So, when you pass e.g. an int x to f(const T& param), then const is being discarded during the pattern matching and T is deduced as int, hence f being instantiated to f(const int&).
f(/*const volatile*/ T arg) - The cv-ness (const/volatile) and the references-ness of the argument is ignored. So if you have something like const int& x = otherx; and pass x to template<class T> void f(T param), then T is deduced as int.
f(T&& arg) - T is deduced as either a lvalue reference (like int&) or a rvalue (like int), depending whether arg is a lvalue or a rvalue, respectively. This case is a bit more complicated, and I advise to read more about forwarding references and reference collapsing rules to understand what's going on.
Scott Meyers discusses template type deduction in Chapter 1 of Effective Modern C++ book in great detail. That chapter is actually free online, see https://www.safaribooksonline.com/library/view/effective-modern-c/9781491908419/ch01.html.

The position of 'const' qualifier of a parameter which is passed by reference in template context

Consider the two following prototypes:
template<class T>
void whatever1(const T& something);
template<class T>
void whatever2(T const& something);
They are both identical. Yet what if T is not a usual type, but a pointer type? For instance, let T be Somewhere* then whatever1 and whatever2 would have different interpretation:
// nonconst pointer (passed by reference) to const object
void whatever1(const Somewhere*& something);
// const pointer (passed by reference) to nonconst object
void whatever2(Somewhere* const& something);
In this case, I can infer the following properties:
1 whatever1:
a) can modify something inside and these changes propagate to the outside;
b) object pointed by something cannot be modified.
2 whatever2:
a) cannot modify something inside so it is safe outside;
b) object pointed by something can be modified.
Usually const and & are used together to avoid copying when passing a parameter and to protect this parameter from modification at the same time. Then in terms of this philosophy only whatever2 fulfills its role. However, if I want the object pointed by something to be also non-modifiable, then none of two is suitable! Then what would be? Maybe this joke:
template<class T>
void whatever3(const T const& something);
In addition to this confusion some people use whatever1 style, while others use whatever2 one. Which one should be used when creating generic classes and methods as a rule of thumb?
Note that if we start considering Somewhere** as T things get even more confusing.
Your assumption is wrong. When T = U *, then T const & and const T & are both U * const &, and that's it. Similarly, const T and T const are both U * const, and when T = const W, then T * is W const * or const W *. (This is all subject to reference collapsing rules, of course, so assume that neither U nor W are reference types).
The replacement of T with Somewhere* when the template is instantiated isn't a textual one - so in both cases, the const will apply to the pointer and not the thing at which it points.
You can't inject a const-qualifier into a named type that way. As a simple example, if you have:
typedef int* P;
const P x;
Then x is of type int* const, not const int*. The const is applied to the whole P type, not just to a part of the P type. The same applies to template type parameters.
If you wanted to inject a const qualifier into the type, you can do so without too much trouble, e.g. by using type traits like remove_pointer and add_const.
They are both identical. Yet what if T is not a usual type, but a pointer type? For instance, let T be Somewhere* then whatever1 and whatever2 would have different interpretation:
// nonconst pointer (passed by reference) to const object
void whatever1(const Somewhere*& something);
// const pointer (passed by reference) to nonconst object
void whatever2(Somewhere* const& something);
No, actually it wouldn't be void whatever1(const Somewhere*& something);, it would be void whatever1(const (Somewhere*)& something); in invalid C++-like code. The C++ way to write it is your whatever2, but they mean the same thing.

Warning for const in typedef

When I was compiling a C++ program using icc 11, it gave this warning:
warning #21: type qualifiers are meaningless in this declaration
typedef const direction_vector_ref_t direction_vector_cref_t;
It is saying const just meaningless. I am curious about this since if this typedef expands it will turn into const array<double,3>& and the const is definitely meaningful. Why it gave this warning?
direction_vector_ref_t, I pressume, its a reference. References are const by design, so adding const to a reference is meaningless. What you probably want is to make a reference to a const object, which can't be done by a typedef. You will have to repeat the slightly modified typedef definition.
Just to clarify:
typedef T& ref;
typedef const T& cref;
typedef const ref cref;
The last line is the same as the first one, not the second one. Typedefs are not token pasting, once you typedef T& as ref, then ref refers to the reference to T type. If you add const to it, then you get a const reference to T type, not a reference to a const T type.
Are you sure? Try:
array<double, 3> a;
direction_vector_cref_t b = a;
b[0] = 1.0;
The issue here, is that when you use a typedef, it conceptually adds parentheses around the type, so you are conceptually using const (array<double, 3>&) as opposed to (const array<double, 3>)&, so you are not actually making the referent object constant. So your declaration is more like:
typedef array<double, 3>& const direction_vector_cref_t;
And in the above, the const for the variable (rather than the referent type) needs to be deferred till later.

constant references with typedef and templates in c++

I heard the temporary objects can only be assigned to constant references.
But this code gives error
#include <iostream.h>
template<class t>
t const& check(){
return t(); //return a temporary object
}
int main(int argc, char** argv){
const int &resCheck = check<int>(); /* fine */
typedef int& ref;
const ref error = check<int>(); / *error */
return 0;
}
The error that is get is invalid initialization of reference of type 'int&' from expression of type 'const int'
This:
typedef int& ref;
const ref error;
Doesn't do what you think it does. Consider instead:
typedef int* pointer;
typedef const pointer const_pointer;
The type of const_pointer is int* const, not const int *. That is, when you say const T you're saying "make a type where T is immutable"; so in the previous example, the pointer (not the pointee) is made immutable.
References cannot be made const or volatile. This:
int& const x;
is meaningless, so adding cv-qualifiers to references has no effect.
Therefore, error has the type int&. You cannot assign a const int& to it.
There are other problems in your code. For example, this is certainly wrong:
template<class t>
t const& check()
{
return t(); //return a temporary object
}
What you're doing here is returning a reference to a temporary object which ends its lifetime when the function returns. That is, you get undefined behavior if you use it because there is no object at the referand. This is no better than:
template<class t>
t const& check()
{
T x = T();
return x; // return a local...bang you're dead
}
A better test would be:
template<class T>
T check()
{
return T();
}
The return value of the function is a temporary, so you can still test that you can indeed bind temporaries to constant references.
Your code gives error because the const qualifier in const ref error is just ignored because 8.3.2/1 says
Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef (7.1.3) or of a template type argument(14.3), in which case the cv-qualifiers are ignored.`
So error has type int& not const int& .
It's a very common mistake for English speaking people, because of the way the English grammar works.
I consider it extremely unfortunate that the C++ syntax would allow both:
const int // immutable int
int const // immutable int
to have the same meaning.
It doesn't make it easier, really, and isn't composable since:
const int* // mutable pointer to immutable int
int* const // immutable pointer to mutable int
certainly do NOT have the same meaning.
And this, unfortunately for you, what kicks in here, as #GMan explains.
If you wish to avoid this kind of error in the future, take the habit of qualifying your types (const and volatile) on their right, then you'll be able to treat a typedef as simple text replacement.
To maintain consistency with the Right Left Rule, I prefer to use 'cv' qualifiers like so.
int const x = 2; // x is a const int (by applying Right Left rule)
int const *p = &x; // p is a pinter to const int
In your example, I would write const ref error = check<int>(); like so
ref const error = check<int>(); // parsed as error is a const reference to an integer
As #Prasoon Saurav pointed out, cv qualifiers are ignored when introduced through typedef because as #GMan also says, that cv qualified references are ill-formed.
Therefore the declaration is effectively as below, which of course is an error.
int &error = check<int>();
Check out this for more information.
This is compiled:
typedef const int& ref;
ref error = check<int>();
VC++ compiler gives some explanation of your error: qualifier applied to reference type; ignored. Reference type must be declared as constant, const cannot be applied later.