Home > Blockchain >  Why is returning a const from a function not being detected as a const?
Why is returning a const from a function not being detected as a const?

Time:05-09

I have a program which depends on the result of std::is_same_v <const value_t, decltype(value)>. However, I have found that when functions are passed to this expression the result is unexpected, causing me bugs.

I thought that functions returning const value_t were going to be treated as being the same as const value_t, but this seems not to be the case since std::is_same_v <value_t, decltype(func())> is what returns true.

I tried returning this values using std::as_const, using static_cast, returning it from a constexpr function, but none of them worked as expected.

A minimal, reproducible example:

#include <type_traits>
#include <iostream>
   
inline const int x = 1;
/// A constant integer variable.
   
inline const int y() {return x;}
/// A constant integer function returning <x>.
   
int main()
{
    /// <x> successfully passes as being a constant.
    std::cout << std::is_same_v <const int, decltype(x)> << " ";
   
    /// But returning it from a function (<y>) does not.
    std::cout << std::is_same_v <const int, decltype(y())> << std::endl;
}                     

Why is this the case? How can I ensure that std::is_same_v <const value_t, decltype(value)> and std::is_same_v <const value_t, decltype(func())> both return true?

CodePudding user response:

y() is a prvalue expression. The type of this expression is not const int, but int.

This is because the type of prvalue non-class non-array expressions have their cv-qualifiers stripped.

In other words, it will work if you use a class type, but not with non-class types.

This is just how the language works. There is no difference between a const int and a int prvalue and similar for other fundamental types. They are just values for which the qualifiers aren't useful.

In contrast the expression x is a lvalue, not a prvalue, and therefore the cv-qualifier is not stripped. There is a difference between referring to an object via const-qualified and non-const-qualified lvalue.

But even then decltype applied directly to an non-parenthesized name doesn't actually consider the type of the expression anyway, but instead considers the type of the declaration of the named entity. This is a special case. decltype((x)) would consider the expression type and yield const int&, adding the lvalue reference because x is a lvalue.

std::invoke_result is also specified to return the decltype of the INVOKE expression, so it will have the same issue.


You can obtain the const-qualified type from the return type of the function type. A typical approach to that is partial specialization based on the function type. Unfortunately doing this properly is quite cumbersome since a lot of specializations must be written out to cover all cases. It also won't work if y is overloaded or a generic lambda/functor.

My guess would be that e.g. boost::callable_traits::return_type is implemented that way and will yield the const-qualified int.

It produces the expected result (see https://godbolt.org/z/7fYn4q9vs):

#include <type_traits>
#include <iostream>
#include <boost/callable_traits/return_type.hpp>
   
inline const int x = 1;
/// A constant integer variable.
   
inline const int y() {return x;}
/// A constant integer function returning <x>.
   
int main()
{
    /// <x> successfully passes as being a constant.
    std::cout << std::is_same_v <const int, decltype(x)> << " ";
   
    /// And returning it from a function (<y>) now does as well.
    std::cout << std::is_same_v <const int, boost::callable_traits::return_type_t<decltype(y)>> << std::endl;
}

CodePudding user response:

This is because of expr#6 which states:

If a prvalue initially has the type cv T, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.

This means that in your particular example, since the call expression y() is a prvalue of type const int and so the above quote applies to it and thus it will be adjusted to int before further analysis since int is a built in type and not a class type and hence the result is false in this case.


On the other hand decltype(x) gives the declared type of x which is const int and not int and hence the result is true in this case.

Note that even if you were to write decltype((x)) then also the result will be true because when x is used as an expression it is an lvalue and so the above quote does not apply to it(and not adjustment is made to const int).

  • Related