0

I'm stuck in fixing this gcc warning : I got tree version of the method "registerCalBack", each of them takes a different "callable" introduced through std::function. Depending on various callable type I declare, I can compile or not, gcc issuing a warning "call of overloaded registerCallBackxxxxx is ambiguous".

I know overloading can be resolved by compiler considering arguments, and not return type, but in that case I failed to understand why gcc is seeing ambiguity : to me, each TCallBack... I defined are different in their argument, and when I change return type of the third one, it compiles... this is really confusing me. I guess part of the problem comes from the fact some parameters are actually incomplete type, but this is how they are accessible from SDL headers, so I reproduced it the example I provide in this thread.

In comment in the code you got examples of definitions that compiled and other that not.

I hope some of you will understand better than me, right now I do not know where to look. Many thanks in advance.

here the gcc command line to compile:

-pedantic -W -Wall -Wextra -std=c++2a -Weffc++ -Wfatal-errors -Winit-self -Wnon-virtual-dtor -Winline -Wmissing-declarations -Wunreachable-code -Wshadow -Wswitch-enum -fstack-protector -Wstack-protector -O0

P.

#include <iostream>
#include <functional>

//This is how SDL_Renderer and SDL_Texture are declared in SDL.h, as incomplete type declaration, to make it opaque
//I reproduce it here with other name, to avoid the need to install SDL if you want to test
struct RENDERER;
typedef struct RENDERER RENDERER;

struct TEXTURE;
typedef struct TEXTURE TEXTURE;

//this is stupid, just to make test
struct dumb;
typedef struct dumb dumb;

class ClassUsingCallBacks // an instance of this class will use callbacks
{
public:

    typedef std::function < RENDERER* (void) > TCallBack_GetRenderer;
    typedef std::function < TEXTURE* (const std::string&) > TCallBack_GetTexture;

    //this works: 
    // typedef std::function < dumb* (void) >
    // typedef std::function < dumb* (TEXTURE*) >   
   // typedef std::function < int (TEXTURE*) >  
    // typedef std::function < TEXTURE* (TEXTURE*) > 

    // BUT THIS FAILED TO COMPILE : 
    // typdef std::function < void (TEXTURE*) >
    // typdef std::function < void* (TEXTURE*) >
    // typedef std::function < void (const std::string&, int, int, int) 

    typedef std::function < void (TEXTURE*) > TCallBack_removeTexture;

    virtual ~ClassUsingCallBacks() {};

    void registerCallBack(TCallBack_GetRenderer cb) {
        std::cout << "Register a TCallBack_GetRenderer" << std::endl;
        getRenderer = cb;
    }

    void registerCallBack(TCallBack_GetTexture cb) {
        std::cout << "Register a TCallBack_GetTexture" << std::endl;
        getTexture = cb;
    }

    void registerCallBack(TCallBack_removeTexture cb) {
        std::cout << "Register a TCallBack_removeTexture" << std::endl;
        removeTexture = cb;
    }

    //to test registered callbacks
    void makeCalls(void) {
        if (getRenderer) getRenderer();
        if (getTexture)  getTexture("a name");
        //not this one since it's the one we failed to implement :/
        // if (removeTexture) removeTexture();
    }

protected:

    TCallBack_GetRenderer   getRenderer {};
    TCallBack_GetTexture    getTexture  {};
    TCallBack_removeTexture removeTexture {};
};

class ClassWithCallBacks
{
public:

    virtual ~ClassWithCallBacks() {};

    RENDERER* getRenderer(void) {
        std::cout << "Inside getRenderer" << std::endl;
        return nullptr;
    }

    TEXTURE* getTexture(const std::string& s) {
        (void)s;
        std::cout << "Inside getTexture" << std::endl;
        return nullptr;
    }

    void removeTexture(TEXTURE* t) {
        (void)t;
        std::cout << "Inside removeTexture" << std::endl;
    }
};

int main(int argc, char **argv)
{
    (void)argc;
    (void)argv;

    std::cout << "entering main" << std::endl;

   ClassWithCallBacks   calledObject; 
    ClassUsingCallBacks user;

    auto cb_1 = std::bind(&ClassWithCallBacks::getRenderer, calledObject);
    user.registerCallBack(cb_1);    

    auto cb_2 = std::bind(&ClassWithCallBacks::getTexture, calledObject, std::placeholders::_1);
    user.registerCallBack(cb_2);    

    user.makeCalls();

    std::cout << "Leaving main" << std::endl;

    return 0;
}
Lpat
  • 11
  • 2

1 Answers1

0

std::bind produces a callable object that takes arbitrary number of arguments, and simply discards those that don't need to be forwarded to the bound callable.

std::function<void(Something)> accepts a callable that returns a result, then simply discards this result.

Therefore, cb_1 can be accepted by both std::function<RENDERER* (void)> and std::function<void (TEXTURE*)>. It can take (and ignore) TEXTURE* parameter, and the function can ignore its return value.


Fundamentally, you are relying heavily on type erasure, but then hoping that the types you are erasing would nevertheless help guide overload resolution. Personally, I would have given the three registerXXX functions different names, reflecting the kind of callback they are registering. No overloading, no problem.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • hummm I think I got it, but need to study a little bit more again ;) All my issue is related to both variadic template and type erasure (I did not know that)... This makes sense now. You're right, my first idea was to implement a `registerXX` per callback ,but then thought only one (with overloading) could be more elegant... but due to your explanation it will lead to more or less quickly to the issue, and maybe decrease maintainability. I will read more about what you said, it's interesting. Thanks a lot. – Lpat Apr 08 '19 at 06:16