Reputation: 402
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
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
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
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