Search code examples
c++template-argument-deductioninvoke-result

Why std::invoke_result_t gives other return type for a callable than a trait?


Snippet:

#include <functional>

template <typename T>
struct CallableTrait;

template <typename R, typename... Args>
struct CallableTrait<std::function<R(Args...)>>
{
    using ReturnType = R;
};

template <typename Callable>
using CallableTraitT = CallableTrait<decltype(std::function{std::declval<Callable>()})>;

const int foo(const int x)
{
    int r = x + 42;
    return r;
}

int main()
{
    using ReturnT1 = CallableTraitT<decltype(foo)>::ReturnType;
    using ReturnT2 = std::invoke_result_t<decltype(foo), decltype(42)>;

    static_assert(std::is_same_v<ReturnT1, const int>);
    static_assert(std::is_same_v<ReturnT2, int>);
}

Demo

Why does std::invoke_result_t strip off the const from the foo return type?

I would like to wrap a callable into another callable with the exact same return type, but apparently I cannot rely std::invoke_result_t for this.


Solution

  • Because that's how the language works.

    A function's return type can be nominally const-qualified (and that's what you're getting out of CallableTrait), but the actual result of invoking such a function (i.e. the expression containing the function call) has the const stripped off if it's a built-in type (for being useless), and that's the thing that invoke_result is telling you about.

    [expr.type/2]: 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.

    Admittedly this is a bit of an oddity, but that's how it is.