1

I am currently wiring an application that has several implementations for a single purpose. It is checked at runtime if the appropriate implementation can be used or if a fallback is used.

For that purpose I want all implementations to implement a static function static bool is_available().

As static functions cannot be made abstract in the underlying base class, is there some preprocessor magic that will allow me to output an error message if the method is not statically implemented in the child class?

YSC
  • 38,212
  • 9
  • 96
  • 149
Nidhoegger
  • 4,973
  • 4
  • 36
  • 81
  • why is `is_available()` static? – Stephan Lechner Feb 28 '19 at 07:56
  • Are you trying to enforce (at compile time) that all subclasses of a base have a static member function `is_available`? – Indiana Kernick Feb 28 '19 at 07:57
  • I dont want to instanciate the classes unneeded. The `is_available` will simple check if libs can be loaded using `dlopen`, so there is no need yet to create an instance of that class to check the availability. – Nidhoegger Feb 28 '19 at 07:58
  • @Kerndog73 exactly – Nidhoegger Feb 28 '19 at 07:58
  • 2
    I think https://stackoverflow.com/questions/10957924/is-there-any-way-to-detect-whether-a-function-exists-and-can-be-used-at-compile should answer your question. – Martijn Otto Feb 28 '19 at 08:00
  • 2
    You might be able to use CRTP. All subclasses would have to inherit from `CanCheckAvailablility` (you'll think of a better name). The CRTP base would just call `is_available` in an unevaluated context and fail to compile if the function is not available. There might be better solutions. That's just the first that came to mind. – Indiana Kernick Feb 28 '19 at 08:02
  • instead of using static function how about using virtual private functions in base class? – Mayur Feb 28 '19 at 08:07
  • Won't this be enforced at compile-time by the code that calls `is_available`? I have a solution to a problem that I don't really understand. – Indiana Kernick Feb 28 '19 at 08:12
  • 1
    I think this is http://xyproblem.info/ – Mayur Feb 28 '19 at 08:15
  • In order to check that the function exists, you will need to try to call it. Even if you use a CRTP base, you will still need to try to call a function on the base. That may be a destructor. You have to fully qualify a function to check if it exists so whatever check you perform will have to be done for each subclass. If you're writing a check in all the subclasses then you might as well just implement `is_available` in the all the subclasses! If you want an automatic check then you'll need to use an abstract virtual function. The compiler will ensure that it is implemented. – Indiana Kernick Feb 28 '19 at 08:32

3 Answers3

2

In the same vein that user9400869's answer, you can define a trait using SFINAE to check, at compilation time, the availability of your classes:

#include <type_traits>

template<class LIB, class = void>
struct is_available
: std::false_type
{};

template<class LIB>
struct is_available<LIB, std::enable_if_t<std::is_invocable_r<bool, decltype(LIB::is_available)>::value>>
: std::integral_constant<bool, LIB::is_available()>
{};

template<class LIB>
constexpr bool is_available_v = is_available<LIB>::value;

This implies C++17 and constexpr functions is_available for the libs:

#include <iostream>

struct A {};
struct B { static constexpr bool is_available() { return false; } };
struct C { static constexpr bool is_available() { return true; } };

int main()
{
    std::cout << is_available_v<A> // 0
              << is_available_v<B> // 0
              << is_available_v<C> // 1 :)
              << '\n';
}

Full demo


If C++17 is not an option, you can implement std::is_invocable_r using C++14 features only.

If constexpr static member functions are not an option for your library classes, you cannot rely on std::true_type and std::false_type and must use run-time result gathering:

#include <type_traits>

template<class LIB, class = void>
struct is_available
{
    static bool value() { return false; }
};

template<class LIB>
struct is_available<LIB, std::enable_if_t<std::is_invocable_r<bool, decltype(LIB::is_available)>::value>>
{
    static bool value() { return LIB::is_available(); }
};

Full demo

YSC
  • 38,212
  • 9
  • 96
  • 149
1

you could test this at compiletime with templates. It goes something like this (sorry not tested):

template<class Type_to_test, class Technical_Detail = void>
struct has_isAvilable : std::false_type {}

template<class Type_to_test>
struct has_isAvilable<Type_to_test, std::enable_if_t<
  std::is_same<bool,decltype(Type_to_test::is_available())>::value
> > : std::true_type {}

Then you can use somewhere in your code:

static_assert(has_isAvilable<Implementation>::value,"Usefull error message");

Where Implementation is the class you wish to test. Have a look at std::type_traits for examples of this.

0

I would suggest an alternative: tag dispatching:

template <typename T> struct Tag {};

struct Base { /**/ };
struct Child1 : Base { /**/ };
struct Child2 : Base { /**/ };

bool is_available(Tag<Base>) {/*..*/}
bool is_available(Tag<Child1>) {/*..*/}
bool is_available(Tag<Child2>) {/*..*/}

Tag "blocks" Inheritance, contrary to:

struct Base { static constexpr bool is_available() { return false; } };
struct Child1 : Base { static constexpr bool is_available() { return true; } };
struct Child2 : Base { /**/ };

static_assert(Base::is_available() == false);
static_assert(Child1::is_available() == true);
static_assert(Child2::is_available() == false); // Call Base::is_available()
Jarod42
  • 203,559
  • 14
  • 181
  • 302