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;
 }
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
pepero
  • 7,095
  • 7
  • 41
  • 72
  • 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. –  Aug 01 '17 at 18:05
  • @Frank, thanks, the code was mainly kept from the solution. – pepero Aug 01 '17 at 18:07

2 Answers2

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&>{};
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 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 Aug 01 '17 at 18:32
  • @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. – Yakk - Adam Nevraumont Aug 01 '17 at 18:36
  • 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. – Chris Uzdavinis Aug 01 '17 at 19:00
  • 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. – fish2000 Aug 02 '17 at 10:02
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.

Jonas
  • 6,915
  • 8
  • 35
  • 53