2

I have the following Header/Source files:

// foo.h
#ifndef __FOO_H
#define __FOO_H

#include <map>
#include <stdexcept>

template <typename T>
class FooBase {

public:
    std::map<float,T> a;
    FooBase(std::map<float,T> a_) : a(a_) {};
    T func(float x1, T fallback) const;
    virtual T func(float x1) const = 0;
};

class Foo: public FooBase<float> {
public:
    Foo() : FooBase<float>({}) {};
    float func(float x1) const;
    // float func(float x1, float fallback) const;
};
void test_foo1();
void test_foo2();

#endif // __FOO_H
// foo.cpp
#include "foo.h"

template <typename T>
T FooBase<T>::func(float x1, T fallback) const {
    try {
        return (func(x1));
    } catch(std::runtime_error&) {
        return fallback;
    }
};

float Foo::func(float x1) const {
    return 2.0;
};

template class FooBase<float>;

void test_foo1() {
    Foo foo;
    float b = foo.func(1.0, 2.0);
}
void test_foo2() {
    Foo foo;
    float b = foo.func(1.0);
}

It is crucial, that the "fallback"-Version of func has the same name as a virtual function but different signature, whereas the single-signature version of func will be specified in the child class Foo.

The test_foo1-Function won't compile:

[  3%] Building CXX object foo.cpp.o
foo.cpp: In function ‘void test_foo1()’:
foo.cpp:20:29: error: no matching function for call to ‘Foo::func(double, double)’
   20 |  float b = foo.func(1.0, 2.0);
      |                             ^
foo.cpp:12:7: note: candidate: ‘virtual float Foo::func(float) const’
   12 | float Foo::func(float x1) const {
      |       ^~~
foo.cpp:12:7: note:   candidate expects 1 argument, 2 provided

If I activate the commented declaration line in foo.h, it compiles, but won't be able to resolve when test_foo1 is called in another source file. In this case I used a catch2-Test which fails when test_foo1 is included, while test_foo2 makes no problems:

[ 98%] Linking CXX executable ...
/usr/bin/ld: libFoo.so: undefined reference to `Foo::func(float, float) const'

Can anyone tell me if I'm totally wrong in how to achieve this? I hesitate to share the full logic of the build, because possibly the repeated declaration might be wrong approach anyways. I hope that fact that test_foo2 works in the other source file generates enough trust to rule out problems located outside of the shared code.

The basic idea is in the GOF-language a "template-method pattern" with a hook-function that has no default. Special is that the hook-method has the same name as the function calling it. The fact that the class has state (a) and a template parameter might make an effect, at least I think that I achieved something like this in simpler situations, therefore I included the state and the template parameter in the "minimal example", hope that does not make things too complicated.

Thanks for any help.

flonk
  • 3,726
  • 3
  • 24
  • 37

1 Answers1

3

Foo::func hides FooBase::func. You can use using to pull FooBase::func into Foos scope:

class Foo: public FooBase<float> {
public:
    Foo() : FooBase<float>({}) {};
    float func(float x1) const;
    using FooBase<float>::func;
};

Live Demo

Alternatively you can explicitly pick FooBase::func to be called:

void test_foo1() {
    Foo foo;
    float b = foo.FooBase<float>::func(1.0, 2.0);
}
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • I know that the complication could be avoided when using two different names for the two signature-versions. However, given that I want it this way, just a reflection on readability: This code is hard to understand for me because the `using` does not refer to the concrete sginature at all, making it seem wrong because the 1-arg `func` is *not* used from `FooBase`. Moreover it is very impractical, as without `using` everything is in principle completely defined, as it were in the case where 1- and 2-arg-`func` would have different names. – flonk Sep 26 '22 at 17:40
  • This forces me to write this line into every child class when deriving multiple child-classes. Is this something special about C++ which one could read more about somewhere? – flonk Sep 26 '22 at 17:41
  • I'm not critizing your answer nor C++, I just want to be sure to understand it better and not yet accept the solution, because it does not fit with my (possibly wrong) intuition that it should be simpler to write a template method with undefined hooks. – flonk Sep 26 '22 at 17:43
  • you can find details here: https://en.cppreference.com/w/cpp/language/overload_resolution. In a nutshell, `foo.func(whatever)` first tries to find `func` in `foo`. It finds a member function and stops (it doesnt look further in base classes). `foo::func` has wrong signature hence the error. If you find this cumbersome, simply give the two functions different names – 463035818_is_not_an_ai Sep 26 '22 at 17:50
  • "making it seem wrong because the 1-arg func is not used from FooBase" thats not correct (or I misunderstand what you want to say), though the 1-arg `func` is overriden – 463035818_is_not_an_ai Sep 26 '22 at 17:52
  • Thanks for Your explanations. You are technically correct, I just meant that the function is in principle implemented in `Foo` not `FooBase`. Again, I don't argue or criticise, after seeing Your (perfect) solution, I just wanted to get the maximum understanding, instead of ticking it off as "works for me", I'm new to C++, will look deeper at overload resolution. – flonk Sep 26 '22 at 18:01
  • @flonk just another example: consider the 1-arg func would not be pure virtual and you do not override it in `foo` and you do have a `func` with different number of arguments, then you'd also need `using` to call the virtual func: https://godbolt.org/z/3Kq3jzr1q. – 463035818_is_not_an_ai Sep 26 '22 at 18:33
  • @flonk maybe you find it "nicer" to qualify the call like this: https://godbolt.org/z/KWjYs99zx – 463035818_is_not_an_ai Sep 26 '22 at 18:35