4

Today I wrote code for my project, and got unresolved external of linker, code must generate class with multiple virtual abstract methods - as base of class collections. So I decide use variadic templates for this task - but got error.

template<int I>
struct pin_tag {};

//inputs
template<int N, class T0, class... VAR>
class inputs_base : public inputs_base<N + 1, VAR...>
{
protected:
   typedef inputs_base<N + 1, VAR...> base_type;
   using arg_type = T0;
   //using base_type::_in;
   virtual void _in(T0 const& t, pin_tag<N>) = 0;
};

template<int N, class T0>
class inputs_base<N, T0>
{
protected:
   using arg_type = T0;
   virtual void _in(T0 const& t, pin_tag<N>) = 0;
};

template<class... VAR>
class inputs : public inputs_base<0, VAR...>
{
private:
   using inputs_type = inputs<VAR...>;
   using inputs_base_type = inputs_base<0, VAR...>;

   template <int N, class T = inputs_type>
   struct getter
   {
     using next_type = typename getter<N - 1, typename T::base_type>;
     using arg_type = typename next_type::arg_type;
     using current_type = typename next_type::current_type;
   };

   template <class T>
   struct getter<0, T>
   {
     using arg_type = typename T::arg_type;
     using current_type = typename T;
   };

   public:
   template<int N>
   using in_arg_type = typename getter<N>::arg_type;

   template<int N>
   void in(in_arg_type<N> const& t)
   {
     getter<N>::current_type::_in(t, pin_tag<N>());
   }
};


class test : public inputs< int, bool >
{
protected:
   virtual void _in(int const& val, pin_tag<0>)
   {}
   virtual void _in(bool const& val, pin_tag<1>)
   {}
 };

int _tmain(int argc, _TCHAR* argv[])
{

   test t;
   t.in<0>(100500);
}

I've got linker error

error LNK2019: unresolved external symbol "protected: virtual void __thiscall inputs_base<0,int,bool>::_in(int const &,struct pin_tag<0>)" (?_in@?$inputs_base@$0A@H_N@@MAEXABHU?$pin_tag@$0A@@@@Z) referenced in function "public: void __thiscall inputs<int,bool>::in<0>(int const &)" (??$in@$0A@@?$inputs@H_N@@QAEXABH@Z)

It seems _in lost virtuality. Can anyone inspect my code please.

Cœur
  • 37,241
  • 25
  • 195
  • 267

1 Answers1

3

Solving the problem

In inputs<...>::in(), replace

getter<N>::current_type::_in(t, pin_tag<N>());

by

this->_in(t, pin_tag<N>());

Also, in inputs_base, restore line

using base_type::_in;

that is currently commented out.

See live example.

_in "lost virtuality" because you are explicitly calling it on base class getter<N>::current_type. Instead, this->_in(...) calls it as a virtual function.

But for this to work, you need all _in overloads in scope; using does this. Overwise, only the _in overload of the most derived base is visible. All others are hidden.

Cleaning up


You could simplify your code by using std::tuple_element instead of (or, to help you implement) your custom getter. Or at least remove getter from inputs and put it (or, a variant) in some more general "utilities" place; you'll definitely need it elsewhere.

Also,

using inputs_type = inputs<VAR...>;

is not needed. You can use inputs directly without appending <VAR...>. This is done automatically by the compiler for your convenience within the scope of inputs definition.

Here is your cleaned-up code in action. The implementation of inputs_base and inputs drops from 51 to 21 lines of code.

Now inputs_base is doing only one job: declare multiple overloads of _in(). Also, defining in() as a template function, neither getter nor std::tuple_element is needed. In particular, inputs is simplified merely down to

template<class... U>
struct inputs : inputs_base<0, U...>
{
    template<int N, typename T>
    void in(T const& t) { this->_in(t, pin_tag<N>()); }
};

Generalizing


Your code assumes each virtual function _in overload is void and takes a single input argument, that is a reference to const. This can be a limitation. Generalizing this approach to handle a set of arbitrary function signatures would make this code extremely useful.

Here is the generalized code in action. It's not much longer. Now test is modified as follows:

struct test : overload<void(int), void(bool)>
// ...

that is, it is parametrized in terms of entire function signatures rather than single-argument types. Call by value is just fine for int or bool; no need for const&.

iavr
  • 7,547
  • 1
  • 18
  • 53