Use of 'Auto Func(Int)' Before Deduction of 'Auto' in C++14

Use of 'auto func(int)' before deduction of 'auto' in C++14

This is [dcl.spec.auto/11]:

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.
Once a non-discarded return statement has been seen in a function,
however, the return type deduced from that statement can be used in
the rest of the function, including in other return statements.
[ Example:

auto n = n;                     // error, n's type is unknown
auto f();
void g() { &f; } // error, f's return type is unknown
auto sum(int i) {
if (i == 1)
return i; // sum's return type is int
else
return sum(i-1)+i; // OK, sum's return type has been deduced
}

 — end example ]

To translate this into English: the compiler needs to know the return type before you can use the function. In case of auto used like this, this is typically achieved by moving the definition before the point of use. If you don't actually need to use return type deduction, you can keep the definition after the use if you provide a signature, including the return type, in the declaration.

use of decltype(auto) func before deduction of auto

Every source file (.cpp file or .cc file) has to stand on its own. The standard calls these files translation units, because the behaviour of the program is the same as-if all those files are translated into machine language as separate units, and only then the machine code is linked into one program.

Standing "on its own" means that the source file together with all files included in it have to convey all information to translate the source file. If you put the masterMethod in one source file and main in a different source file, the compiler has no idea which type is returned by masterMethod when compiling main.

The answer to your question

How would one seperate implementation and declaration as I would like to do it?

Is thus: Either you put the source code of the function in the header file, or you give up on using return type deduction. If you put the source code into the header file, you don't need to put it inside the class definition, as long as you declare it inline.

Use of 'auto [...] 'before deduction of 'auto' with recursive, concept-based function template

There are two problems here.

The first problem is yours:

namespace rng {
template <std::ranges::range Rng>
auto deep_flatten(Rng&& rng) {
using namespace std::ranges;

if constexpr (range<Rng>) { // <==
return deep_flatten(rng | views::join);
} else {
return rng | views::join;
}
}
}

This function is infinitely recursive. deep_flatten is constrained range<Rng>, so the if constexpr check there is always going to be true, so we're never going to enter the base case. This is just a bug - we're checking the wrong thing, it's not if we're a range, it's if our underlying value is a range. That's:

namespace rng {
template <typename Rng>
auto deep_flatten(Rng&& rng) {
using namespace std::ranges;

auto joined = rng | views::join;
if constexpr (range<range_value_t<decltype(joined)>>) {
return deep_flatten(joined);
} else {
return joined;
}
}
}

And here we get into the second problem, which is the standard library's problem. What rng | views::join means is:

The name views​::​join denotes a range adaptor object ([range.adaptor.object]). Given a subexpression E, the expression views​::​join(E) is expression-equivalent to join_­view{E}.

But join_view{E} for an E that's already a specialization of join_view... is a no-op right now because of class template argument deduction (CTAD) - the copy deduction candidate is the best candidate, so our nested join operation actually becomes a single join. Your original implementation gets around this problem because it's not join-ing a join_view, it's always join-ing vectors.

I've submitted LWG 3474.

In the meantime, we can work around the views::join problem by just directly using join_view and specifying the template argument explicitly:

namespace rng {
template <typename Rng>
auto deep_flatten(Rng&& rng) {
using namespace std::ranges;

auto joined = join_view<views::all_t<Rng>>(rng);

if constexpr (range<range_value_t<decltype(joined)>>) {
return deep_flatten(joined);
} else {
return joined;
}
}
}

This works.

How do I deduce auto before a function is called?

No, there is not.

Even ignoring the practical problems (requiring multi-pass compilation, ease of making undecidable return types via mutually recursive type definitions, difficulty in isolating source of compilation errors when everything resolves, etc), and the design issues (that forward declaration is nearly useless), C++11 was designed with ease of implementation in mind. Things that made it harder to write a compiler needed strong justification.

The myriad restrictions on auto mean that it was really easy to slide it into existing compilers: it is among the most supported C++11 features in my experience. C++14 relaxes many of the restrictions, but does not go nearly as far as you describe. Each relaxation requires justification and confidence that it will be worth the cost to compiler writers to implement.

I would not even want that feature at this time, as I like the signatures of my functions to be deducible at the point I call them, at the very least.

use of boost::hana::eval_if_t before deduction of auto

First, in case the path doesn't make it clear, boost/hana/fwd/eval_if.hpp is but a forward declaration. If you want to use it in earnest, you need to include the whole thing, i.e., boost/hana/eval_if.hpp. That's the cause of the original error.

Then, this bool is wrong:

constexpr bool b = is_a<std::reference_wrapper<std::string>,
arg_t>;

Coercing it to bool means that the type no longer carries the value information. Use auto.

Recursive lambda fails to compile if return type is not specified

When I try to compile this, I get the following message:

main.cpp: In instantiation of 'main()::<lambda(auto:1, int)> [with auto:1 = main()::<lambda(auto:1, int)>]':
main.cpp:10:17: required from here
main.cpp:7:18: error: use of 'main()::<lambda(auto:1, int)> [with auto:1 = main()::<lambda(auto:1, int)>]' before deduction of 'auto'
7 | _func(_func, n - 1);
| ~~~~~^~~~~~~~~~~~~~

The key is

error: use of 'lambda' before deduction of 'auto'

A simpler example would be something like

#include<iostream>

auto f(int x) {
std::cout << x << " ";
if (x > 0)
f(x - 1);
}

int main() {
f(3);
}

which gives much the same error.

In essence, the compiler can't know until its finished processing the function what return type it needs to be, but it can't finish processing the function until it works out what f is returning. There's a circular dependency here, so the compiler gives an error.

See also Use of 'auto func(int)' before deduction of 'auto' in C++14.

Why can't the type of my class-static auto function be deduced within the class scope?

While the way you have written the code makes it appear possible, the in-class definition of foo() can only be processed after the class is fully defined. It is as if you wrote this:

struct Foo
{
static auto foo(int bar);

typedef std::result_of<decltype(Foo::foo)> foo_t;
};

auto Foo::foo(int bar)
{
return 0;
}

The definition of foo() is allowed to use types defined in class Foo, including foo_t, which would be circular. Therefore, the definition of class Foo is not allowed to use the definition of its member functions--only their declarations.

In other words, you assume the code is fully evaluated from the top to the bottom. It is not.

What are some uses of decltype(auto)?

Return type forwarding in generic code

For non-generic code, like the initial example you gave, you can manually select to get a reference as a return type:

auto const& Example(int const& i) 
{
return i;
}

but in generic code you want to be able to perfectly forward a return type without knowing whether you are dealing with a reference or a value. decltype(auto) gives you that ability:

template<class Fun, class... Args>
decltype(auto) Example(Fun fun, Args&&... args)
{
return fun(std::forward<Args>(args)...);
}

Delaying return type deduction in recursive templates

In this Q&A a few days ago, an infinite recursion during template instantiation was encountered when the return type of the template was specified as decltype(iter(Int<i-1>{})) instead of decltype(auto).

template<int i> 
struct Int {};

constexpr auto iter(Int<0>) -> Int<0>;

template<int i>
constexpr auto iter(Int<i>) -> decltype(auto)
{ return iter(Int<i-1>{}); }

int main() { decltype(iter(Int<10>{})) a; }

decltype(auto) is used here to delay the return type deduction after the dust of template instantiation has settled.

Other uses

You can also use decltype(auto) in other contexts, e.g. the draft Standard N3936 also states

7.1.6.4 auto specifier [dcl.spec.auto]

1 The auto and decltype(auto) type-specifiers designate a placeholder
type that will be replaced later, either by deduction from an
initializer or by explicit specification with a trailing-return-type.
The auto type-specifier is also used to signify that a lambda is a
generic lambda.

2 The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq,
conversion-function-id, or trailing-return-type, in any context where such a declarator is valid. If the function
declarator includes a trailing-return-type (8.3.5), that specifies the declared return type of the function.
If the declared return type of the function contains a placeholder type, the return type of the function is
deduced from return statements in the body of the function, if any.

The draft also contains this example of variable initialization:

int i;
int&& f();
auto x3a = i; // decltype(x3a) is int
decltype(auto) x3d = i; // decltype(x3d) is int
auto x4a = (i); // decltype(x4a) is int
decltype(auto) x4d = (i); // decltype(x4d) is int&
auto x5a = f(); // decltype(x5a) is int
decltype(auto) x5d = f(); // decltype(x5d) is int&&
auto x6a = { 1, 2 }; // decltype(x6a) is std::initializer_list<int>
decltype(auto) x6d = { 1, 2 }; // error, { 1, 2 } is not an expression
auto *x7a = &i; // decltype(x7a) is int*
decltype(auto)*x7d = &i; // error, declared type is not plain decltype(auto)


Related Topics



Leave a reply



Submit