Overloaded Lambdas in C++ and Differences Between Clang and Gcc

Overloaded lambdas in C++ and differences between clang and gcc

Looks like a Clang bug to me.

The general rule is that member functions of the same name in different base classes do not overload. For example:

struct Foo { void bar(); };
struct Baz { void bar(int); };
struct Quux : Foo, Baz { };

int main() { Quux().bar(); } // error on both GCC and Clang

For whatever reason, Clang fails to diagnose this ambiguity for operator().

A using-declaration lifts the named base class members to the derived class scope, allowing them to overload. Hence:

struct Quux_2 : Foo, Baz { using Foo::bar; using Baz::bar; };
Quux_2().bar(); // OK.

In the working version of the code, the using declarations recursively bring every operator() declaration in the template arguments into the scope of the most derived class, allowing them to overload.

gcc vs clang - ambiguous overload when using `make_overload` variadic lambda inheritance

I can give you a workaround.

template <typename... TFs>
struct overload_set : TFs...
{
overload_set(TFs... fs) : TFs(fs)... {}
};

here, we inherit from a bunch of distinct parent types, each with an operator(). These do not (at least in gcc) overload the way you want.

To fix this, we inherit linearly and carry () down via using:

template<class...Ts>
struct inherit_linearly;
template<>
struct inherit_linearly<>{};
template<class T0, class...Ts>
struct inherit_linearly<T0, Ts...>:
T0, inherit_linearly<Ts...>
{
using T0::operator();
using inherit_linearly<Ts...>::operator();
template<class A0, class...As>
inherit_linearly( A0&&a0, As&&...as ):
T0(std::forward<A0>(a0)),
inherit_linearly<Ts>(std::forward<As>(as)...)
{}
};

now we replace overload_set as follows:

template <typename... TFs>
struct overload_set : inherit_linearly<TFs...>
{
using inherit_linearly<TFs...>::operator();
overload_set(TFs... fs) :
inherit_linearly<TFs...>(std::forward<TFs>(fs)...)
{}
};

and both gcc and clang should like it.

The linear inheritance is sub-optimal: a balanced binary tree is better, but would take more work. (basically, you take a pack Xs... and you split it into Xs_front... and Xs_back... using careful TMP, put those in a types<...> pack, transcribe those to your two parents, and do the using blah::operator() thing). This is because the compiler has a limit on recursive template instantiations and inheritance depth that tends to be more shallow than the limit of total template instantiations and inheritance "volume".


In c++17 we don't have to do this linear inheritance:

template <typename... TFs>
struct overload_set : TFs...
{
using TFs::operator()...;
overload_set(TFs... fs) : TFs(fs)... {}
};

because they added a new spot you can do ... expansion.

Overloading structs with template call operator and generic lambdas - gcc vs clang

I think this is a gcc bug (submitted as 80767), running afoul of [temp.inst]/9:

An implementation shall not implicitly instantiate a function template, a variable template, a member template, a non-virtual member function, a member class, a static data member of a class template, or a substatement of a constexpr if statement, unless such instantiation is required.

The instantiation of the generic lambda's operator() with auto = a is not required, hence it should not be instantiated.

Clang and GCC disagree on whether overloaded function templates are ambiguous

clang++ is correct because both functions matches equally good. I'm unsure which compiler that is correct, but...

A C++11 solution could be to just add the requirement that the Rest part must contain at least one type and that is easily done by just adding R1. That would mean that the rest of your code could be left unchanged:

template<typename Fn, typename H, typename R1, typename... R>
inline
void feh(Fn& fn, const std::tuple<H, R1, R...>*)
{
fn(H{});
using Rest = const std::tuple<R1, R...>*;
feh<Fn, R1, R...>(fn, static_cast<Rest>(nullptr));
}

A C++17 solution would be to remove the other feh overloads and use a fold expression:

template <typename Fn, typename... H>
inline void feh(Fn& fn, const std::tuple<H...>*) {
(..., fn(H{}));
}

This is a unary left fold over the comma operator which "unfolded" becomes:

(((fn(H1{}), fn(H2{})), ...), fn(Hn{}))

Why are there differences of compiled c++17 lambdas between GCC and clang?

Calling lambdas other than in return statement, both compilers behave differently.

They may output different assembly, but they have the exact same behavior. This is what the Standard guarantees, nothing else. So in that regard, both are correct, as they both return 20.

constexpr expressions don't have to be evaluated at compile time if it is possible. There is nothing prohibiting implementations to call add instead of optimizing it out in the following example:

constexpr int add(int lhs, int rhs) {
return lhs + rhs;
}

int main() {
return add(4, 5);
}

They can, but that doesn't mean that they must. The Standard tries to give implementations as much freedom as possible when compiling, to allow heavy optimizations for example, which is in part the reason why undefined behavior and things like ill-formed; no diagnostics required exist (correct me if I'm wrong).

Do note that compiling with -O3 results in both compilers returning 20 in your three examples, so no difference there.

Overload resolution behaviour difference between GCC and clang (SFINAE)

Both Clang and g++ are correct here.

Per Luc Danton's answer, a compiler is permitted to deduce T = int for the foo function template. Then, during the substitution of that value into the declaration of foo, the implicit instantiation of meta<int> is required, and it results in an error outside the immediate context of the substitution (so SFINAE does not apply). So Clang is correct to reject this code.

However, [temp.inst]p7 says:

If the overload resolution process can determine the correct function to call without instantiating a class template definition, it is unspecified whether that instantiation actually takes place.

Because the non-template foo is an exact match for the arguments in the call, a compiler could determine that a function template specialization will never be the best viable function, and so need not perform argument deduction and substitution. Therefore g++ is correct to not reject this code.

clang vs gcc - empty generic lambda variadic argument pack

I've reported the issue as gcc bug #68071.

Why do gcc and clang give different results in aggregate initialization?

U is a type which claims convertibility to any other type. And "any other type" includes whatever T is provided to the f templates.

Therefore, 1 is always a potentially legitimate overload; SFINAE permits it to exist.

A is an aggregate of 3 elements. As such, it can be initialized by an initializer list containing anywhere from 0 to 3 elements. C++20 allows aggregates to undergo aggregate initialization with constructor syntax.

Therefore, 3, 2, 1, and 0 are all potentially legitimate overloads; SFINAE permits them to exist. Under pre-C++20 rules, none of these would be aggregate initialization, so none of them (save 1, which works because of the aforementioned property of U) would be valid SFINAE overloads.

Clang doesn't implement C++20's rule for aggregate initialization as of yet, so as far as Clang is concerned, the only available overload is X<1>.

For more fully functional C++ compilers, the question is this: which overload is better by C++'s overload resolution rules?

Well, all of the available overloads involve implicit conversions from the argument type X<4> to one of its base classes. However, overload resolution based on base class conversions gives priority to base classes that are closer to the type of the argument in the inheritance graph. As such, X<3> is given priority over X<2> even though both are available.

Therefore, by C++20's rules and overload resolution, 3 ought to be the right answer.

gcc accepts and clang rejects this code with nested generic lambdas, why?

This program is ill-formed, no diagnostic required, so both implementations (indeed, any implementation) are correct. The rule violated is [temp.res.general]/6.4:

a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter

The template here is the innermost operator(): it uses the specialization of the immediately containing operator() (with the very closure type of which it is a member as a template argument), which has a deduced return type. "Immediately following" here is (still) within the very return statement from which that type shall be deduced, so the instantiation would be ill-formed due to just h(h), which does not involve a template parameter of that template (i.e., the type of n).

GCC and MSVC don't bother doing the semantic check for the deduced return type until the final instantiation (with int for the 10), by which point the return type is known: it's just the appropriate variant of the innermost lambda. If that innermost lambda is not generic, however, the check takes place during instantiation of the containing operator(), and the same error occurs.

This difference of behavior can be seen in a much simpler example:

auto g();
template<class T> auto f(T x) {return g()-x;}

Clang has already rejected at this point, but GCC accepts, perhaps followed by

auto g() {return 1;}
int main() {return f(1);}


Related Topics



Leave a reply



Submit