0

I'm using inheritance with a set of classes. One of the child classes takes in an std::function(ReturnTy<ParamTypes...>), along with the ParamTypes arguments. The class signature and constructor look like:

template<class ReturnTy, class... ParamTypes>
class Child : public Interface
{
public:
    Child(ReturnTy default_value, ParamTypes... args)
        : func_set_(false)
        , m_Args(std::make_tuple(std::forward<ParamTypes>(args)...))
        , m_ReturnValue(default_value)
    {}

private:
    bool func_set_;
    std::function<ReturnTy(ParamTypes...)> m_Funciton;
    std::tuple<ParamTypes...> m_Args;
    ReturnTy m_ReturnValue;
};

My issue is when I want to specialize for the case where there are no parameters. Furthermore, I also want to specialize for the case which ReturnTy=void and there are parameters. I found an answer that is close to what I'm looking for here, but it doesn't exactly cover what I'm trying to do because that question uses compile-time integers as template parameters, where I'm using types. It also concerns functions instead of classes. I feel like I'm close, but I just need some help to make my code work.

For reference, here is what I have for the other specializations (shortened):

template<class ReturnTy>
class Child<ReturnTy> : public Interface
{
public:
    Child(ReturnTy default_value)
        : // The same as first class without m_Args
    {}

private:
    // Same as first class without m_Args
};

template<class... ParamTypes>
class Child<void, ParamTypes...> : public Interface
{
public:
    Child(ParamTypes... args)
        : // Same as first class without m_ReturnValue

private:
    // Same as first class without m_ReturnValue
};



Edit
Specifically, the issue comes from something like the following line of code:

Child<void> obj1(5);
Drake Johnson
  • 640
  • 3
  • 19
  • Just so everyone is aware, I've omitted methods from the class that are not relevant to my question. For example, there is a `set_function()` method that will initialize the `std::function`, and there is a virtual `run()` function from the interface that does `m_ReturnValue = std::apply(m_Function, m_Args)`. – Drake Johnson Feb 06 '20 at 22:03
  • In case of `Child`, which specialization should be selected? The one with `ReturnTy` only or the one with `void` in first place? – max66 Feb 06 '20 at 22:12
  • @max66 I *want* the one with just `void` to be selected, but I'm not sure how to tell the compiler to select that one. – Drake Johnson Feb 06 '20 at 22:15
  • Sorry: not clear for me: do you mean the last one: `class Child`? – max66 Feb 06 '20 at 22:17

2 Answers2

1

The problem is that your specializations are of the same level (no one is more specialized that the other) and Child<void> matches both.

If you want that Child<void> matches the Child<ReturnTy> case (otherwise the solution is simple and elegant: in the second specialization, split the ParamTypes... list in a Par0 mandatory type and the rest of the ParamTypes...) I don't see a simple and elegant solution.

The best I can imagine, at the moment, is add a level of indirection (add a Child_base class) adding also a template parameter to explicit the desired solution.

Maybe can be made in a simpler way (sorry but, in this moment, I can try with a compiler) but I imagine something as follows

template <typename RT, bool, typename ... PTs>
class Child_base : public Interface
{
   // general case (no empy PTs... list and no void return type)
};

template <typename ... PTs>
class Child_base<void, true, PTs...> : public Interface
{
   // case return type is void (also empy PTs... list)
};

template <typename RT>
class Child_base<RT, false> : public Interface
{
   // case return type only, but not void, and empy PTs
};

template <typename RT, typename ... PTs>
class Child 
   : public Child_base<RT, std::is_same_v<void, RT>, PTs...> 
 {
 };

This way, Child<void> inherit from Child_base<void, true> that matches the first specialization of Child_base but doesn't match the second one.

I propose another way about Child: instead of define it as a class derived from Child_base, you can try defining it as a using alias of Child_base

template <typename RT, typename ... PTs>
using Child = Child_base<RT, std::is_same_v<void, RT>, PTs...>;

Maybe renaming Child_base with a more appropriate name.

max66
  • 65,235
  • 10
  • 71
  • 111
  • Sorry for the stupid question, but will I need to still specify a constructor for `Child`, or will the one from `Child_base` be used by default? – Drake Johnson Feb 07 '20 at 02:42
  • @DrakeJohnson - Not a stupid question at all, IMHO: it's a little complicated. I'm not an expert but, as far I know, if for "used" do you mean that the derived class can explicitly call a base class constructor (if accessible), to initialize the base class, yes: it can. And a default base class constructor can default initialize the base component in the derived class. But if for "used" do you mean that a base class can be inherited from the derived class and used as a constructor for the derived class, the answer is: no by default, yes using `using` (but with `using` only from C++11)(continue – max66 Feb 07 '20 at 18:22
  • @DrakeJohnson - anyway, I suppose that is more useful if you read the "Inheriting Constructor" section in the [cpp reference using declaration page](https://en.cppreference.com/w/cpp/language/using_declaration) – max66 Feb 07 '20 at 18:24
  • @DrakeJohnson - anyway, I've improved my answer adding an example of use of `using` that should completely wipe away the inheritance problem – max66 Feb 07 '20 at 18:25
  • @DrakeJohnson - sorry: in my first comment, " But if for "used" do you mean that a base class can be inherited from the derived class and used as a constructor for the derived class" is wrong: it should be " But if for "used" do you mean that a base class **constructor** can be inherited from the derived class and used as a constructor for the derived class" – max66 Feb 07 '20 at 18:29
  • @DrakeJohnson - thinking a little better about this problem (and probing it with a compiler)... the second template parameter can be a `bool`, not necessarily a `typename`; so `true` and `false` instead of `std::true_type` and `std::false_type`; answer simplified. – max66 Feb 07 '20 at 19:27
  • Thank you! I appreciate the revisions and explanations. The typedef is certainly a clever and (in my opinion) cleaner way to go about it. A question about the typedef: Would I need to declare it as a typename (e.g. `= typename Child_base (etc.)`? – Drake Johnson Feb 08 '20 at 20:19
  • @DrakeJohnson - I'm agree: the `typedef` way (well... the `using` way... a `typedef` can't be used for a template alias; `using` is more powerful) is cleaner. Anyway... sorry but I don't understand your question. – max66 Feb 08 '20 at 20:30
0

issue is that Child<void> matches 2 (partial) specializations (where none are more specialized than the other):

  • template<class ReturnTy> class Child<ReturnTy> with [ReturnTy = void]
  • template<class... ParamTypes> class Child<void, ParamTypes...> with empty pack.

You so need extra specialization:

template<>
class Child<void> : public Interface
{
public:
    Child() = default;

// ....
private:
    std::function<void()> m_Function;
};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302