6

If you have a function that if constexpr () decides to do one thing or the other, how to return an lvalue in one case and an rvalue in the other case?

The following example does not compile in the first usage line, because the return type auto is no reference:

static int number = 15;

template<bool getref>
auto get_number(int sometemporary)
{
    if constexpr(getref)
    {
        return number; // we want to return a reference here
    }
    else
    {
        (...) // do some calculations with `sometemporary`
        return sometemporary;
    }
}

void use()
{
    int& ref = get_number<true>(1234);
    int noref = get_number<false>(1234);
}
max66
  • 65,235
  • 10
  • 71
  • 111
Aart Stuurman
  • 3,188
  • 4
  • 26
  • 44

2 Answers2

13

how to return an lvalue in one case and an rvalue in the other case?

I suppose you can try with decltype(auto) and a couple of parentheses

template<bool getref>
decltype(auto) get_number() // "decltype(auto)" instead of "auto"
{
    if constexpr(getref)
    {
        return (number); // not "number" but "(number)"
    }
    else
    {
        return 123123; // just a random number as example
    }
}
max66
  • 65,235
  • 10
  • 71
  • 111
  • 1
    @AartStuurman - mainly an intuition... I remember that the difference between `auto` and `decltype(auto)` is that `decltype(auto)` is more flexible and useful to obtain informations about references (or not). And the parentheses is because `decltype(number)` return `int` where `decltype((number))` return `int &`. – max66 Mar 22 '19 at 14:40
  • I never even knew about decltype(auto), and the parentheses to make an lvalue are also logical if you think about it. Weird stuff, thanks. – Aart Stuurman Mar 22 '19 at 14:47
4

std::ref seems to do the trick for me:

#include <functional>
#include <iostream>

static int number = 15;

template<bool getref>
auto get_number()
{
    if constexpr(getref)
    {
        return std::ref(number); // we want to return a reference here
    }
    else
    {
        return 123123; // just a random number as example
    }
}

int main(int argc, char **argv)
{
    int& ref = get_number<true>();
    int noref = get_number<false>();

    std::cout << "Before ref " << ref << " and number " << number << std::endl;
    ref = argc;
    std::cout << "After ref " << ref << " and number " << number << std::endl;

    std::cout << "Before noref " << noref << " and number " << number << std::endl;
    noref = argc * 2;
    std::cout << "After noref " << noref << " and number " << number << std::endl;
}

Try it online!

As expected, changing ref changes number (and not noref), while changing noref changes nothing else.

Since the behavior is constexpr and templated, returning std::ref of number forces it to actually make a reference.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • 4
    You may want to note that doing this "breaks" `auto& ref = get_number();`. http://coliru.stacked-crooked.com/a/31de626e49387a49 – NathanOliver Mar 22 '19 at 14:37
  • 1
    @NathanOliver: Hmm... Good point. The caller would have to explicitly call `.get()` on the result to use `auto&`, or explicitly declare the variable `int&` without it. Not great. [max66's solution](https://stackoverflow.com/a/55301861/364696), while rather more magical looking/less intuitive, does seem to work identically storing to both `auto&` and `int&`, so I'd probably recommend their solution (with a quick inline comment to explain the magic). Gave them an up-vote. :-) – ShadowRanger Mar 22 '19 at 14:45
  • I find your solution more intuitive, but max66's solution (sadly) has better results. :) – Aart Stuurman Mar 22 '19 at 14:49