Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using auto and decltype to return reference from function in templated class

How can I coerce a function in a templated class to return a reference to a member variable using auto/decltype?

Here's a trivialized example of what I'm trying to do. Suppose you've got a templated class that stores something in a private member variable, a_ as follows:

#include <iostream>

template <typename T>
class A
{
private:
  T a_;

public:
  A(T a) : a_(a) {}

  // 1. Return const reference to a_
  const T & get() const { return a_; }

  // 2. Return non-const reference to a_
  T & get() { return a_; }
};

int main(int argc, char *argv[])
{
  A<int> a(3);

  const auto & a1 = a.get(); // 1. Return const reference to a_
  //a1 = 4;  // Shouldn't compile
  std::cout << "Value of a = " << a.get() << std::endl;

  auto & a2 = a.get(); // 2. Return non-const reference to a_
  a2 = 5;
  std::cout << "Value of a = " << a.get() << std::endl;

  return 0;
}

The expected/desired output is:

Value of a = 3
Value of a = 5

But now, suppose I want the compiler to deduce the type returned by the const and non-const get() functions in A<T> and I want to ensure both calls return references to a_.

My best guess is currently:

template <typename T>
class A
{
private:
  T a_;

public:
  A(T a) : a_(a) {}

  // 1. Return const reference to a_
  const auto get() const -> std::add_lvalue_reference<const decltype(a_)>::type
  {
    return a_;
  }

  // 2. Return non-const reference to a_
  auto get() -> std::add_lvalue_reference<decltype(a_)>::type
  {
    return a_;
  }
};

but that fails to compile. The first error given by GCC is:

decltype.cpp:11:29: error: expected type-specifier
decltype.cpp:11:26: error: expected ‘;’ at end of member declaration
decltype.cpp:11:29: error: ‘add_lvalue_reference’ in namespace ‘std’ does not name a type

The motivation for this lies outwith my distilled example code, but stems from an attempt to reduce the number of parameters a template takes when one (or more) of those parameters is used solely to specify a return type which the compiler should (I think) be able to deduce by itself. Note: in the real world, the return type of get() is not that of a_, but is the return type of some function f(a_) which I know to be deducible by the compiler. Thus my need for auto/decltype in this example.

The thing that's puzzling me is that the compiler can deduce the return type correctly using near-identical code in a non-templated class:

class A
{
private:
  int a_;

public:
  A(int a) : a_(a) {}

  // 1. Return const reference to a_
  const auto get() const -> std::add_lvalue_reference<const decltype(a_)>::type
  {
    return a_;
  }

  // 2. Return non-const reference to a_
  auto get() -> std::add_lvalue_reference<decltype(a_)>::type
  {
    return a_;
  }
};

Any help to understand what I'm missing will be greatly appreciated.

Details:

Centos 6.5
gcc (GCC) 4.7.2 20121015 (Red Hat 4.7.2-5)
like image 612
duncan Avatar asked Jul 13 '14 18:07

duncan


People also ask

Can you use auto as a return type in C++?

In C++14, you can just use auto as a return type.

What is decltype Auto in C++?

The decltype type specifier yields the type of a specified expression. The decltype type specifier, together with the auto keyword, is useful primarily to developers who write template libraries. Use auto and decltype to declare a template function whose return type depends on the types of its template arguments.

Which C++ added feature of Auto for return type of function?

C++: “auto” return type deduction The “auto” keyword used to say the compiler: “The return type of this function is declared at the end”. In C++14, the compiler deduces the return type of the methods that have “auto” as return type.

What is the difference between auto and decltype Auto?

'auto' lets you declare a variable with a particular type whereas decltype lets you extract the type from the variable so decltype is sort of an operator that evaluates the type of passed expression.


2 Answers

Just to mention it, you don't actually have to use std::add_lvalue_reference to get the behaviour you want. This works just as well and is more readable in my book.

template <typename T>
class A {
    private:
        T a_; 

    public:
        A(T a) : a_(a) {}

        const auto get() const -> const decltype(a_) & {
            return a_; 
        }

        auto get() -> decltype(a_) & {
            return a_; 
        }
};

int main() {
    A<int> a(1);
    cout << a.get() << endl;
    a.get() = 2;
    cout << a.get() << endl;
}
like image 89
Stian Svedenborg Avatar answered Oct 16 '22 13:10

Stian Svedenborg


Wrap a_ in an extra pair of parentheses in the trailing return type to get the type of the expression a_ instead of the declared type of the variable a_ (Live at Coliru):

// 1. Return const reference to a_
auto get() const -> decltype((a_))
{
  return a_;
}

// 2. Return non-const reference to a_
auto get() -> decltype((a_))
{
  return a_;
}

or if C++1y is available:

// 1. Return const reference to a_
auto& get() const
{
  return a_;
}

// 2. Return non-const reference to a_
auto& get()
{
  return a_;
}
like image 31
Casey Avatar answered Oct 16 '22 12:10

Casey