3

I was checking some solutions of the book cpp template metaprogramming for the 1st exercise http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?CPPTM_Answers_-_Exercise_2-0

Write a unary metafunction add_const_ref that returns T if it is a reference type, and otherwise returns T const&

 template<typename T>
 struct add_const_ref
 {
     typedef typename boost::add_const<T>::type ct;
     typedef typename boost::add_reference<ct>::type type;
 };

I revised it with c++11:

 template<typename T>
 struct add_const_ref_type
 {
     typedef typename std::add_const<T>::type ct;
     typedef typename std::add_lvalue_reference<ct>::type type;
 };

i do not understand why it works with reference. I expect this will add const, i.e., change the int& to `const int&.

 int main()
 {   
    std::cout << std::is_same<add_const_ref_type<int &>::type, int&>::value << '\n'; // print 1
    std::cout << std::is_same<add_const_ref_type<int &>::type, const int&>::value << '\n'; // print 0

    return 0;
 }
2
  • 1
    Slightly off-topic, but valuable advice: if you are using C++11, you really want to use using instead of typedef, it's so much more legible. Commented Aug 1, 2017 at 18:05
  • @Frank, thanks, the code was mainly kept from the solution. Commented Aug 1, 2017 at 18:07

2 Answers 2

8

You have been fooled by the great error of placing your type modifiers on the left.

const int&

I mean, it looks like when you apply const to int&, you get const int& right?

Wrong.

To understand what is going on here, apply type modifiers on the right. The modifier always applies to the thing to its right; for now, pretent putting it "on the left" is illegal:

int const&

that is a reference to an int that is const. This is actually valid C++.

int& const

that is nonsense; you cannot apply const to a &. & themselves are already immutable; only what they refer to can be changed.

When you add_const<int&>, you logically get int& const; the trait knows this is nonsense, so returns int&.

Now we step back and look at what happens if you put it on the left. If there is nothing on the left, a special rule makes it apply to the leftmost. So:

const int&

is just

int const&

and

using X=int&;
const X

is

X const

again, by the "on the left is just on the right of the leftmost part" rule. After this is applied, we then expand X:

int& const

This would be quite obvious is you always applied type modifiers on the right, and ignored the special case for type modifers on the left.


Fixing this, if you want to add const to value types and make references refer to a const instance, is easy to patch over, but solving it generically is hard. After all, a reference is a kind of alias, as is a pointer; when you add const to a pointer, you get a const pointer, not a pointer to const.

I suspect the assignment wants you to solve the problem from scratch, not use std or boost.

template<class T>
struct tag_t {using type=T;};
template<class T>
struct add_const_ref:tag_t<T>{};
template<class T>
struct add_const_ref<T&>:tag_t<T const&>{};
Sign up to request clarification or add additional context in comments.

4 Comments

awesome! some questions: 1. "on the left is just on the right of the leftmost part", never heard about such rule, is it rule about applying const? can you elaborate? 2. then I guess add_lvalue_reference will do nothing since it is already an lvalue reference, right? your answer is the same as Zedd's answer in solution.
@pepero It just describes what the const in const blah**& connects to -- the blah, assuming it contains no more *s or & or whatever else. It is sort of implied by the grammar more than anything, and it is a special case. If you get in the habit of applying const and volatile on the right everything is simple and makes sense.
type declarations can be read right-to-left for arbitrary numbers of "const", "volatile", pointers, etc. int const * const ** would be (right to left) a pointer to a pointer to a const pointer to a const int. For example.
I’m a huge fan of the general use of T const&-style qualification, for exactly these stated reasons; additionally I would also second the commenter above who notes that the C++11 using keyword’s syntax are a marked improvement over that of the venerable and pre-C++ typedef statement – and while both of these syntax choices are, of course, arguably aesthetic and subjective, the language properties with which they are concerned are fundamental and worthy of contemplation.
2

It works because int & const does not make any sense (a reference is always const), thus std::add_const<int &>::type is the same as int &. That is, the part that is being made const is not int.

Here is an example:

#include <iostream>
#include <type_traits>

int main() {
    std::cout << std::is_same<int &, std::add_const<int &>::type>::value << std::endl;
    std::cout << std::is_same<int, std::add_const<int>::type>::value << std::endl;
    std::cout << std::is_same<int const, std::add_const<int>::type>::value << std::endl;

    std::cout << std::is_same<int &, std::add_lvalue_reference<std::add_const<int &>::type>::type>::value << std::endl;
    std::cout << std::is_same<int &, std::add_lvalue_reference<std::add_const<int>::type>::type>::value << std::endl;
    std::cout << std::is_same<int const &, std::add_lvalue_reference<std::add_const<int>::type>::type>::value << std::endl;
}

Online here.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.