Question

I'd like to call a function AsJson in a unified way irrespective of whether I'm dealing with an instance or a primitive type, or anything else.

I thought I could define an abstract base class which classes could inherit, and then define a template function that calls the AsJson member function when the template is specialized to appropriate classes. For other types, they could just specialize the template function.

Something like this:

#include <iostream>

class IInstrumented {
 public:
  virtual void AsJson() const = 0;
};

template <typename T>
void AsJson(const T&);

template <typename T>
void AsJson(const IInstrumented& instrumented) {
  instrumented.AsJson();
}

class Foo : public IInstrumented {
 public:
  void AsJson() const override { std::cout << "A Foo!" << std::endl; }
};

template <>
void AsJson(const int& x) {
  std::cout << "An integer!" << std::endl;
}

int main() {
  Foo foo;
  AsJson(foo);

  int x = 3;
  AsJson(x);
}

Unfortunately, this results in the following linker error:

special.cpp:(.text+0x56): undefined reference to `void AsJson<Foo>(Foo const&)'

Is this approach workable as is? Is the fix something relatively minor, or is an entirely different approach warranted?

Was it helpful?

Solution 2

You should change

template <typename T>
void AsJson(const IInstrumented& instrumented) {
  instrumented.AsJson();
}

to

template <typename T>
void AsJson(const T& instrumented) {
  instrumented.AsJson();
}

And then:

$ ./bla 
A Foo!
An integer!

Now if you want to ensure that T is derived from IInstrumented. You should add (c++11 only) :

template <typename T>
void AsJson(const T& instrumented) {
  static_assert(std::is_base_of<IInstrumented, T>::value,
        "T must be a descendant of IInstrumented"
        );
  instrumented.AsJson();
}

Then with

class Bar {
 public:
  void AsJson() { std::cout << "A Foo!" << std::endl; }
};

The following code:

Bar bar;
AsJson(bar);

raises:

bla.cpp: In instantiation of ‘void AsJson(const T&) [with T = Bar]’:
bla.cpp:38:13:   required from here
bla.cpp:13:3: error: static assertion failed: T must be a descendant of IInstrumented
   static_assert(std::is_base_of<IInstrumented, T>::value,

See also How to ensure that the template parameter is a subtype of a desired type?

OTHER TIPS

Although there is an accepted answer, I thought I would point out why you had the error in the first place.

When compiling

AsJson(foo);

the compiler is looking for this template instantiation:

template <>
void AsJson(const Foo&);

which is not defined(undefined reference error) because

template <typename T>
void AsJson(const T&);

has no implementation. It is not using this template specialization:

template <>
void AsJson(const IInstrumented& instrumented) {
    instrumented.AsJson();
}

because it is not the best candidate while deducting type. By changing the code to:

AsJson<IInstrumented>(foo);

You then explicitly tell the compiler which template specialization you want to use. Previous answer assume all types will inherits from IInstrumented which might not be the case hence why the static_assert checking for type was added.

By providing a default implementation, you can avoid the necessity to inherits from IInstrumentable. Adding an assert in there might help catching serializing unknown types.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top