3

So for the purpose of example let's say I have 3 simple structs, the second of which doesn't contain a bar method:

struct one {
    void foo(const int);
    void bar();
};

struct two {
    void foo(const int);
};

struct three {
    void foo(const int);
    void bar();
};

Then I have a struct which will manage objects of these types:

struct owner {
    map<int, one> ones;
    map<int, two> twos;
    map<int, three> threes;

    template <typename T, typename Func>
    void callFunc(T& param, const Func& func) {
        func(param);
    }

    template <typename T>
    void findObject(int key, const T& func) {
        if(ones.count(key) != 0U) {
            callFunc(ones[key], func);
        } else if(twos.count(key) != 0U) {
            callFunc(twos[key], func);
        } else {
            callFunc(threes[key], func);
        }
    }

    void foo(const int key, const int param) { findObject(key, [&](auto& value) { value.foo(param); } ); }
    void bar(const int key) { findObject(key, [&](auto& value) { value.bar(); } ); }
};

When I try to compile this I get:

error: struct two has no member named bar

Is there a way that I can work around this?

Live Example

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288

1 Answers1

11

First, the utilities. One is our favorite overload that shows off three C++17 features, and overloads the operator() of several function objects, all in two lines.

template<class... Ts> struct overload : Ts... { using Ts::operator()...; };
template<class... Ts> overload(Ts...) -> overload<Ts...>;

Then an accepts-everything fallback tag type:

struct fallback_t { template<class T> fallback_t(T&&) {} };

Now to the call itself. We make the original generic lambda SFINAE-friendly by putting the value.bar() call into the trailing return type, then overload it with a fallback overload that has undefined behavior if actually invoked (since OP "omit[ted] any explicit definition of behavior"):

void bar(const int key) { 
    findObject(key, overload {
          [&](auto& value) -> decltype(void(value.bar())) { value.bar(); },
          [](fallback_t){ fire_missiles_and_impregnate_cat(); }
    } ); 
}
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 4
    I am fine with missiles, but the cat! – SergeyA Feb 16 '18 at 22:32
  • 2
    I like this, but I'm sure OP would appreciate an explanation of why this works. – Ryan Haining Feb 16 '18 at 22:54
  • Should your second line use `auto`? – Ken Wayne VanderLinde Feb 16 '18 at 23:00
  • @KenWayneVanderLinde: It would be ambiguous when `bar()` exists. – Jarod42 Feb 16 '18 at 23:21
  • OK, I want to accept this, but I'm kinda against accepting code only answers. I actually see what you're doing here but the next person may not. Could you just give one sentence of explanation? – Jonathan Mee Feb 19 '18 at 15:16
  • Well this is annoying :( It doesn't work in Visual Studio. I've asked a question here: https://stackoverflow.com/q/48870308/2642059 Is there any hope for a workaround? – Jonathan Mee Feb 19 '18 at 16:29
  • *"fallback overload that has undefined behavior"* I would prefer to say implementation specific ;-) – Jarod42 Feb 19 '18 at 17:10
  • You wrap `value.bar()` in `void`... why? What's going on there, my actual `bar` needs to return a value but I cannot here. – Jonathan Mee Feb 19 '18 at 18:20
  • `template overload(Ts...) -> overload;` What is this, syntactically? Looks like a function declaration to me with a trailing return type. But that requires an `auto` in front of the function name, so I think I'm missing something here. – Ken Wayne VanderLinde Feb 19 '18 at 19:11
  • @JonathanMee Your lambda returns `void`; I'm just following suit. There's no particular reason for the cast otherwise. – T.C. Feb 19 '18 at 19:12
  • @KenWayneVanderLinde That's a deduction guide. – T.C. Feb 19 '18 at 19:12
  • @T.C. So adding in return types like this: https://ideone.com/q6b7Y4 you can see me floundering on the line: `[&](auto& value) -> decltype(void(value.bar())) { value.bar(); }` I'm having trouble creating something there that won't default everything to return void. – Jonathan Mee Feb 19 '18 at 19:17
  • @T.C. Cool, thanks! I forgot that c++17 introduced this convenience for templates. – Ken Wayne VanderLinde Feb 19 '18 at 19:18
  • 1
    @JonathanMee that's your `foo` lambda still returning `void`. – T.C. Feb 19 '18 at 19:20
  • @T.C. Thank you T.T – Jonathan Mee Feb 19 '18 at 19:21
  • @T.C. OK, while you were right on that, but even when I correct that, it always opts for the fallback, even when a good function is available. – Jonathan Mee Feb 19 '18 at 19:28
  • @T.C. Welp, I'm stuck again thought I'd link you to the latest hoop that I can't figure out how to jump through on this: https://stackoverflow.com/q/49057387/2642059 – Jonathan Mee Mar 01 '18 at 19:35