Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is const lost in this template structure?

It is known that the following function pointers have different types:

void foo_int_ref(int&);
void foo_const_int_ref(const int&);

static_assert(
    !std::is_same<
        decltype(foo_int_ref),
        decltype(foo_const_int_ref)
    >::value,
    "Types should be different");

Let's consider this generalization:

template<typename T>
void foo(T t) {}

template<typename T>
struct ref_vs_const_ref {
    typedef decltype(foo<T>) foo_T;
    typedef decltype(foo<const T>) foo_const_T;
};

using int_ref_vs_const_ref = ref_vs_const_ref<int&>;

static_assert(
    !std::is_same<
        typename int_ref_vs_const_ref::foo_T,
        typename int_ref_vs_const_ref::foo_const_T
    >::value,
    "Types should be different"); // -- it fails

The last assertion fails. For some reason, the const is lost for foo_const_T. But why?

like image 218
ivaigult Avatar asked Dec 14 '17 12:12

ivaigult


2 Answers

The const on value parameters does not affect the signature in any shape or form. The same is true when you remove the reference from the non-template declarations. The const only affects the use of the parameter in the function definition. If you add a reference or a pointer to the type things change and the const affects the type of the function.

In your nested types the T is a int& to which the const is applied. However, T& and T& const are also the same types. I guess your confusion stems from your ill-advised placement of const to the left: it is more the const applies to the top-level entity.

like image 131
Dietmar Kühl Avatar answered Sep 19 '22 06:09

Dietmar Kühl


The answer of "why" this is happening has been already given. This is a possible way to resolve your problem -- adding "const" to the nested type:

template<typename T, typename = void>
struct add_value_const
{
    using type = const T;
};

template<typename T>
using add_value_const_t = typename add_value_const<T>::type;

template<typename T>
struct add_value_const<
    T, std::enable_if_t<
        std::is_reference<T>::value
    >
>
{
    using type = std::conditional_t<
        std::is_lvalue_reference<T>::value,
        add_value_const_t<std::remove_reference_t<T>>&,
        add_value_const_t<std::remove_reference_t<T>>&&
    >;
};

template<typename T>
struct add_value_const<
    T, std::enable_if_t<std::is_pointer<T>::value>
>
{
    using type = add_value_const_t<std::remove_pointer_t<T>>*;
};

In your case you would have to use foo<add_value_const<T>> instead of foo<const T>.

I used the C++14 syntax, but it could be ported easily to C++11. With C++17 it is even more readable.

You can find the compiling example on godbolt.

like image 24
dodomorandi Avatar answered Sep 19 '22 06:09

dodomorandi