kc9jud
kc9jud

Reputation: 402

Type alias for deduced auto return type of member function

Is there a way, from within a class definition, to deduce the auto return type of a member function and use it with a using declaration? For example, I want:

#include <vector>

class Foo {
  public:
    Foo() = default;
    auto begin() const { return v_.begin(); }
    auto end() const { return v_.end(); }

  private:
    std::vector<int> v_;
  public:
    using iterator = decltype(std::declval<Foo>().begin());
};

This, of course, gives the compiler error

error: use of 'auto Foo::begin() const' before deduction of 'auto'
   15 |     using iterator = decltype(std::declval<Foo>().begin());
      |                               ~~~~~~~~~~~~~~~~~~~~~~~~~^~

since, presumably, auto will not be deduced until after the type Foo is complete. However, it would seem that, in principle, the compiler does have enough information at the point of using iterator = ... to deduce the type.

One can replace using iterator = ... with template<typename=void> using iterator = ..., but that means that one has to use the type as Foo::iterator<> rather than Foo::iterator, which won't work for a Container.

Upvotes: 1

Views: 292

Answers (3)

Swift - Friday Pie
Swift - Friday Pie

Reputation: 14688

Here we're bitten by rule If the type of an entity with an undeduced placeholder type is needed to determine the type of an expression, the program is ill-formed.

The deduction of return type for class member won't be complete until class itself can be complete. In addition to what 463035818_is_not_a_number wrote, in some cases we may stagger deduction (and separate implementation from interface). Doesn't make sense with simplest classes, but is useful with CRTP.

class FooProps {
private:
    std::vector<int> v_;
public:
    auto begin() const { return v_.begin(); }
    auto end() const { return v_.end(); }
};

class Foo : public FooProps { 
    using iterator = decltype(std::declval<FooProps>().begin());
};

In its own turn FooProps might be a mix-in and then definition of container goes to a trait class.

Upvotes: 2

ecatmur
ecatmur

Reputation: 157444

However, it would seem that, in principle, the compiler does have enough information at the point of using iterator = ... to deduce the type.

You might think so, but it's possible to supply additional information afterward that changes the answer:

// ...
    using iterator = decltype(std::declval<Foo>().begin());

    auto begin() { return v_.begin(); }
    auto end() { return v_.end(); }
};

By adding non-const overload of begin afterward, we change the type of iterator from std::vector<int>::const_iterator to std::vector<int>::iterator.

Instead, write something that does not require Foo to be complete, e.g.:

    using iterator = decltype(std::as_const(v_).begin());

Upvotes: 1

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 123114

You can use either the same expression that is used to deduce the return type of the method, ie declval(v_.begin()):

struct Foo {
    std::vector<int> v_;
    auto begin() const { return v_.begin(); }
    using iterator = decltype(v_.begin());
};

or a trailing return type:

struct Bar {
    std::vector<int> v_;
    auto begin() const -> decltype(v_.begin()) { return v_.begin(); }
    using iterator = decltype(std::declval<Bar>().begin());
};

PS: Do not write a constructor that does nothing. If you want to declare it then make it Foo() = default;.

Upvotes: 3

Related Questions