Are my std::add_lvalue_reference and std::add_rvalue_reference implementations correct?

Hi.

I read that the type_traits header file gives two utilities: add_lvalue_reference and add_rvalue_reference

I didn't find the official implementation on the internet, so I decided to make them by myself

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typename T>
struct add_rvalue_reference
{
   using gtype = T&&;
};

template<typename T>
struct add_rvalue_reference<T&>
{
   using type = T&;
};

//////////////////////////////////////////////

template<typename T>
struct add_lvalue_reference
{
   using type = T&;
};


Do you notice any inconsistencies?

I tested this by myself and it seems to work. But who knows, maybe I'm missing something?
Last edited on
Looks fine to me.

Some test cases:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <type_traits>

// add_lvalue_reference:
//  If T is an object type or a function type that has no cv- or ref- qualifier,
//    provides a member typedef type which is T&. If T is an rvalue reference to
//    some type U, then type is U&. Otherwise, type is T.

// add_rvalue_reference:
//  If T is an object type or a function type that has no cv- or ref- qualifier,
//    provides a member typedef type which is T&&, otherwise type is T.
namespace my {
  template <typename T> struct add_rvalue_reference      { using type = T &&; };
  template <typename T> struct add_rvalue_reference<T &> { using type = T &; };
  template <typename T> struct add_lvalue_reference      { using type = T &; };

  template <typename T> using add_lvalue_reference_t =
    typename my::add_lvalue_reference<T>::type;
  template <typename T> using add_rvalue_reference_t =
    typename my::add_rvalue_reference<T>::type;
}

# define IS_SAME(t,u) static_assert(std::is_same<t, u>::value, "not same");

class T;

// test add_lvalue_reference
template <typename T>
struct test_add_ref {
  IS_SAME(my::add_lvalue_reference_t<T>, std::add_lvalue_reference_t<T>)
  IS_SAME(my::add_lvalue_reference_t<T&>, std::add_lvalue_reference_t<T&>)
  IS_SAME(my::add_lvalue_reference_t<T&&>, std::add_lvalue_reference_t<T&&>)

  IS_SAME(my::add_lvalue_reference_t<const T>,
          std::add_lvalue_reference_t<const T>)
  IS_SAME(my::add_lvalue_reference_t<const T&>,
          std::add_lvalue_reference_t<const T&>)
  IS_SAME(my::add_lvalue_reference_t<const T&&>,
          std::add_lvalue_reference_t<const T&&>)

  IS_SAME(my::add_lvalue_reference_t<volatile T>,
          std::add_lvalue_reference_t<volatile T>)
  IS_SAME(my::add_lvalue_reference_t<volatile T&>,
          std::add_lvalue_reference_t<volatile T&>)
  IS_SAME(my::add_lvalue_reference_t<volatile T&&>,
          std::add_lvalue_reference_t<volatile T&&>)

  IS_SAME(my::add_lvalue_reference_t<const volatile T>,
          std::add_lvalue_reference_t<const volatile T>)
  IS_SAME(my::add_lvalue_reference_t<const volatile T&>,
          std::add_lvalue_reference_t<const volatile T&>)
  IS_SAME(my::add_lvalue_reference_t<const volatile T&&>,
            std::add_lvalue_reference_t<const volatile T&&>)

  // Test add_rvalue_reference
  IS_SAME(my::add_rvalue_reference_t<T>, std::add_rvalue_reference_t<T>)
  IS_SAME(my::add_rvalue_reference_t<T&>, std::add_rvalue_reference_t<T&>)
  IS_SAME(my::add_rvalue_reference_t<T&&>, std::add_rvalue_reference_t<T&&>)
  IS_SAME(my::add_rvalue_reference_t<const T>,
          std::add_rvalue_reference_t<const T>)
  IS_SAME(my::add_rvalue_reference_t<const T&>,
          std::add_rvalue_reference_t<const T&>)
  IS_SAME(my::add_rvalue_reference_t<const T&&>,
          std::add_rvalue_reference_t<const T&&>)

  IS_SAME(my::add_rvalue_reference_t<volatile T>,
          std::add_rvalue_reference_t<volatile T>)
  IS_SAME(my::add_rvalue_reference_t<volatile T&>,
          std::add_rvalue_reference_t<volatile T&>)
  IS_SAME(my::add_rvalue_reference_t<volatile T&&>,
          std::add_rvalue_reference_t<volatile T&&>)

  IS_SAME(my::add_rvalue_reference_t<const volatile T>,
          std::add_rvalue_reference_t<const volatile T>)
  IS_SAME(my::add_rvalue_reference_t<const volatile T&>,
          std::add_rvalue_reference_t<const volatile T&>)
  IS_SAME(my::add_rvalue_reference_t<const volatile T&&>,
            std::add_rvalue_reference_t<const volatile T&&>)
};

template struct ::test_add_ref<int>;
template struct ::test_add_ref<int&>;
template struct ::test_add_ref<int&&>;
template struct ::test_add_ref<int()>;

Also, the exact implementation is vendor-specific, but you can still look at the code:
In libstdc++, for example:
https://github.com/gcc-mirror/gcc/blob/1cb6c2eb3b8361d850be8e8270c597270a1a7967/libstdc++-v3/include/std/type_traits#L1642
> maybe I'm missing something?

Yes, the edge case where T is not a referenceable type.

referenceable type: http://eel.is/c++draft/defns.referenceable

Reference modifications: http://eel.is/c++draft/meta.trans.ref

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <type_traits>

template < typename > struct member_type {} ;

template < typename T, typename C > struct member_type< T C::* > { using type = T ; } ;

template < typename A, typename B > constexpr void assert_is_same()
{ static_assert( std::is_same_v<A,B>, "expected same type" ) ; }

namespace my {

  template <typename T> struct add_rvalue_reference      { using type = T &&; };
  template <typename T> struct add_rvalue_reference<T &> { using type = T &; };
  template <typename T> struct add_lvalue_reference      { using type = T &; };
}

struct A {

    int foo() ;
    int bar() const ;
    int baz() & ;
};

void test() {

    // type 'foo' is int() : it is a referenceable type
    using foo = member_type< decltype(&A::foo) >::type ;

    // type 'bar' is int() const : it is not a referenceable type
    using bar = member_type< decltype(&A::bar) >::type ;

    // type 'baz' is int() & : it is not a referenceable type
    using baz = member_type< decltype(&A::baz) >::type ;

    // reference is added if the type is a referenceable type
    assert_is_same< std::add_lvalue_reference<foo>::type, foo& > () ;
    assert_is_same< std::add_rvalue_reference<foo>::type, foo&& > () ;

    // reference is not added if the type is not a referenceable type
    assert_is_same< std::add_lvalue_reference<bar>::type, bar > () ;
    assert_is_same< std::add_rvalue_reference<bar>::type, bar > () ;
    assert_is_same< std::add_lvalue_reference<baz>::type, baz > () ;
    assert_is_same< std::add_rvalue_reference<baz>::type, baz > () ;

    //////////////////////////////////////////////////////////////////////

    // these two are fine
    assert_is_same< my::add_lvalue_reference<foo>::type, foo& > () ; // fine
    assert_is_same< my::add_rvalue_reference<foo>::type, foo&& > () ; // fine


    // my::add_lvalue_reference and my:add_rvalue_reference will
    // generate errors if the type involved is not a referenceable type

    // these are attempts to form references to non-referenceable types
    using t1 = my::add_lvalue_reference<bar>::type ; // *** error
    using t2 = my::add_rvalue_reference<bar>::type ; // *** error
    using t3 = my::add_lvalue_reference<baz>::type ; // *** error
    using t4 = my::add_lvalue_reference<baz>::type ; // *** error
}
Wow thanks for that both of you!

However, Borges, I tried to compile because I wanted to see the error... but there's not:

http://coliru.stacked-crooked.com/a/21a472fece544a8f

Also, just in case

1) How do I check if a type is referenceable or not?

2) What does the & mean after the declaration of baz()?
Last edited on
> I tried to compile because I wanted to see the error... but there's not

The standard library implementations of std::add_lvalue_reference<> and std::add_rvalue_reference<> will not give an error; they won't try to add a reference to a non-referenceable type.

This would generate the error that you want to see:
1
2
3
4
5
6
int main()
{	
    using bar = member_type< decltype(&A::bar) >::type;
    
    using t1 = g_add_lvalue_reference<bar>::gtype;
}

http://coliru.stacked-crooked.com/a/887a4885e85cf361



> How do I check if a type is referenceable or not?

Here's an inelegant brute-force way of checking referenceability of a type.
(Hopefully, some one can come up with a more compact, elegant version)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <type_traits>

namespace detail {

    template < typename... > struct is_const_qualified : std::false_type {} ;
    template < typename R, typename... A > struct is_const_qualified< R(A...) const > : std::true_type {} ;
    template < typename R, typename... A > struct is_const_qualified< R( A..., ... ) const > : std::true_type {} ;

    template < typename... > struct is_volatile_qualified : std::false_type {} ;
    template < typename R, typename... A > struct is_volatile_qualified< R(A...) volatile > : std::true_type {} ;
    template < typename R, typename... A > struct is_volatile_qualified< R( A..., ... ) volatile > : std::true_type {} ;

    template < typename... > struct is_cv_qualified : std::false_type {} ;
    template < typename R, typename... A > struct is_cv_qualified< R(A...) const volatile > : std::true_type {} ;
    template < typename R, typename... A > struct is_cv_qualified< R( A..., ... ) const volatile > : std::true_type {} ;

    template < typename... > struct is_ref_qualified : std::false_type {} ;

    template < typename R, typename... A > struct is_ref_qualified< R(A...) & > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R(A...) && > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R(A...) const & > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R(A...) const && > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R(A...) volatile & > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R(A...) volatile && > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R(A...) const volatile & > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R(A...) const volatile && > : std::true_type {} ;

    template < typename R, typename... A > struct is_ref_qualified< R( A..., ... ) & > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R( A..., ... ) && > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R( A..., ... ) const & > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R( A..., ... ) const && > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R( A..., ... ) volatile & > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R( A..., ... ) volatile && > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R( A..., ... ) const volatile & > : std::true_type {} ;
    template < typename R, typename... A > struct is_ref_qualified< R( A..., ... ) const volatile && > : std::true_type {} ;
}

template < typename T > struct is_referenceable
                            : std::conditional< detail::is_const_qualified<T>::value ||
                                                detail::is_volatile_qualified<T>::value ||
                                                detail::is_cv_qualified<T>::value ||
                                                detail::is_ref_qualified<T>::value,
                                                std::false_type, std::true_type >::type {} ;

http://coliru.stacked-crooked.com/a/12f84d17c13e7c6e
http://rextester.com/ECQM11561



> What does the & mean after the declaration of baz()?

See the second part of 'const-, volatile-, and ref-qualified member functions' in:
http://en.cppreference.com/w/cpp/language/member_functions
Oh man that's massive :O thanks!

So basically a type is said to be referencable if it is AT LEAST ONE OF THESE

- const
- volatile
- cv qualified
- reference
- false (?)
- true (?)

1) Why did you include cv qualified when you already included const and volatile alone?

2) Why are false_type and true_type referenceable?
Last edited on
A type is a referenceable type if it is one of:
a. an object type
b. a function type that does not have cv-qualifiers and/or a ref-qualifiers
c. a reference type (in this case, reference-collapsing rules would apply)

A type is not a referenceable type if it is a function type with const and/or volatile and/or ref qualifiers.



> Why did you include cv qualified when you already included const and volatile alone?

a. int(int) const, b. int(int) volatile and c. int(int) const volatile are three different function types.



> Why are false_type and true_type referenceable?

They are object types; all object types are referenceable types.
Last edited on
You never stop teaching me new things, are you part of the c++ committee? :D haha

> are you part of the c++ committee?

No.

Cubbi is the one who is quite close to both the standard and the standardisation process.
He is the resident authority on standard C++.
Thanks @JLBorges. I always learn stuff from your posts.

A type is not a referenceable type if it is a function type with const and/or volatile and/or ref qualifiers.
.... Hopefully, some one can come up with a more compact version

Perhaps something like this:
1
2
3
4
5
6
7
template<typename T>
struct is_referenceable: std::integral_constant<bool, 
  std::is_object<T>::value || std::is_reference<T>::value > {};
template<typename T, typename... Args>
struct is_referenceable<T(Args...)>: std::true_type {};
template<typename T, typename... Args>
struct is_referenceable<T(Args..., ...)>: std::true_type {};

http://coliru.stacked-crooked.com/a/708763446d68bd98
Last edited on
> Perhaps something like this

Yes! Thanks!
A huge improvement on the puerile code that I wrote.
Topic archived. No new replies allowed.