2

This is a stripped down version of what happens through 4 layers of indirection. The overlaod resolution breaks down in the vicinity of a local lambda with the same name. Which is some kind of mainetenance issue, especially if the code still builds (here it doesn't) and the error is only caught in tests.

Is there an elegant way to circumvent this? See the Godbolt Playgound for this issue.

#include "catch.hpp"

#include <iostream>
#include <map>
#include <string>

namespace {

struct Something {};

template <typename T>
void process(const T& x, Something const& s){}

struct A {
};

void process(A const& p, Something const& s){}

struct B {
};

void process(B const& p, Something const& s){}
} // namespace

int main {
    struct C {};

    // THIS lanbda shadows the visibility of formerly defined
    // functions with same name. This is a maintainability issue
    auto process = [](C const& p, Something const& s) {};

    Something s{};

    A a{};
    B b{};
    C c{};

    process(a, s);
    process(b, s);
}
Markus W.
  • 307
  • 3
  • 10
  • 11
    `Is there an elegant way to circumvent this?` rename your lambda? – UKMonkey Feb 28 '18 at 10:45
  • Or name your namespace so it can be used when calling the functions from it? – Some programmer dude Feb 28 '18 at 10:46
  • 4
    This isn't anything special about lambdas; all local variables work like this. – melpomene Feb 28 '18 at 10:46
  • It's not even special about local variables. This is basic C++ behavior. Names live in scopes, scopes can be nested, and names in inner scopes hide identical names in outer scopes for the purpose of _unqualified_ name lookup. – MSalters Feb 28 '18 at 10:52
  • @UKMonkey: Note that your proposed resolution works for this simple case. In general, the attempt is to add a new member to an **overload set**. The actual behavior is that the overload set is **replaced**. Your proposed solution doesn't alter the overload set at all. – MSalters Feb 28 '18 at 10:55
  • @MSalters the question was is there an 'elegant way'. Not having the problem in the first place is by far the most elegant. – UKMonkey Feb 28 '18 at 10:57
  • @UKMonkey: With templates, you may very well want to have all functions in a single overload set. Perhaps the best-known case is `swap`. Every `swap` is supposed to be named `swap` and not `Swap` or `localSwap`. Similarly, every `process` might intentionally be named `process` – MSalters Feb 28 '18 at 11:00

4 Answers4

2

There are actually two problems with what you are trying to do:

  • scope shadowing
  • you can't overload a function with a function object (a lambda) out of the box.

To overload two different entities you need a helper class. Fortunately in C++17 it's really easy. A lot of boilerplate that would be required in C++14 is out of the window :)

namespace util
{
template <class... Fs>
struct Overload : public Fs...
{
    using Fs::operator()...;
};

template <class... Fs> Overload(Fs...) -> Overload<Fs...>;
}

And now in your main:

auto process = util::Overload {
    [] (const auto& p, Something const& s) -> decltype(::process(p, s)) {
                 return ::process(p, s);
    },
    [](C const& p, Something const& s) { }
};

The first lambda is there to call the global overloads. The -> decltype(::process(p, s)) is there for proper SFNINAE. In your case it doesn't make a difference, but in more complex situation it will influence overload resolution.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • Great answer, but this is some next level stuff... I having difficulty grasping the concept of these new C++17 structures. It's very far away from good-old C programming. Any good source you can point me to? – JHBonarius Feb 28 '18 at 12:28
  • 1
    @JHBonarius http://www.stroustrup.com/C++11FAQ.html , http://www.stroustrup.com/what-is-2009.pdf , https://isocpp.org/wiki/faq/cpp11 , https://isocpp.org/blog/2017/07/cpp17-14-11-a-cheatsheet-of-modern-c-language-and-library-features-anthony , https://stackoverflow.com/questions/38060436/what-are-the-new-features-in-c17 – bolov Feb 28 '18 at 12:35
  • 1
    @JHBonarius and if you are passionate about C++ there are many great videos from CppCon https://cppcon.org/ , https://www.youtube.com/channel/UCMlGfpWw-RUdWX_JbLCukXg There are a few by Bjarne himself (and Herb I think) specifically about the new features. But you will find a great deal of interesting varying topics. – bolov Feb 28 '18 at 12:40
  • thanks. But many links I knew already. I'm trying to wrap my head around this (what I now know is called) template meta-programming... – JHBonarius Feb 28 '18 at 13:57
2

You might be explicit about overload:

// C++17 implementation, might have similar code for C++11/C++14
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

and then:

auto process = overloaded{
    [](C const& p, Something const& s) {},
    [](const auto& p, Something const& s){::process(p, s);}
};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Interesting, but what does the code mean? What do all the `...` do. e.g. `class...`, `Ts...`, `operator()...;`. I find it difficult to read this without any explanation. – JHBonarius Feb 28 '18 at 12:30
  • 2
    Those `...` is for **variadic template**, So mainly you have `template struct overloaded : T1, T2 { using T1::operator(); using T2::operator();}` but for any number, not just 2. – Jarod42 Feb 28 '18 at 12:42
  • @bolov great link. I didn't know it was called that, so I would not have found it on Cppreference easily. I'm grasping the concept now. Let's think of an application to test it on... – JHBonarius Feb 28 '18 at 18:12
1

Easy solution:

::process(a, s);
::process(b, s);

Even though these overloads live in an anonymous namespace, they can be found by lookup in the surrounding (global) namespace.

Your solution uses unqualified name lookup. Unqualified name lookup starts in the local scope, so it finds a single candidate process.

MSalters
  • 173,980
  • 10
  • 155
  • 350
0

You can use a generic lambda so that all calls invoke this lambda, and process depending on the type of the parameter in the lambda body.

#include <type_traits>

auto process = [](const auto& p, Something const& s) {
                   if /* constexpr */ (std::is_same<std::decay_t<decltype(p)>, C>::value) {
                   // ^^^^^^^^^^^^^^^ C++17 feature
                       /* do something */
                   }
                   else ::process(p, s);
               };

Demo

xskxzr
  • 12,442
  • 12
  • 37
  • 77