1

I have a std::variants containing objects with different interfaces. Goal is to call some of the methods if the object in variant has it. I'm trying to make a couple of template decorators and looking for a way to do it with less boilerplate and without macro. Finally my idea looks like:

class Good
{
public:
    void a(int);
    float b(float);
    int c(double);
};

class Bad
{
public:
    void a(int);
    int c(double);
};

template<class T>
class View : protected T
{
public:
    using T::a;
    using T::b;
};
template<class T, template<class> class V>
constexpr bool IsFitToView = // ??
//usage

std::variant<Good, Bad> variant = //;
std::visit([](auto&& obj) {
    if constexpr(IsFitToView<std::decay_t<decltype(obj)>, View>)
    {
       View view{obj}; // create view from obj
       //here work with obj as view, calling a `a` and `b` methods;
    }
}, variant);

Main problem is how to create IsFitToView check. My approach is:

template<class T, template<class> class V, class = void>
struct IsFit : std::false_type
{};

template<class T, template<class> class V>
struct IsFit<T, V, std::void_t<V<T>>> : std::true_type
{};

template<class T, template<class> class V>
constexpr bool IsFitToView = IsFit<T, V>::value;

I had a hope that it has to work like SFINAE does: View<Good> compiles and template specialization selected, View<Bad> cannot be compiled because of using T::b; in a View. But it returns true both for Good and Bad types!

std::cout << IsFitToView<Good, View> << IsFitToView<Bad, View>;

I know I can check the methods existence by a checking it indvidualy and check it like

if constexpr(HasAFunc<T> && HasBFunc<T> && ...

But I have to create many different Views. It is very verbose and hard to read. Please, could you explain why my approach doesn't work and give any ideas to do what I want. Thanks!

FoxCanFly
  • 23
  • 3

1 Answers1

1

Could you explain why my approach doesn't work ?

Your current approach with using declarations fails, because the class' (View's) body instantiation happens outside of immediate context, which means it cannot result in a soft error that would result in falling back to the IsFit primary template, hence its specialization always produces a better match, resulting in IsFitToView always being true.

Even if that worked, using T::a; wouldn't tell you anything about a. It could be a single function, an overloaded set of functions, a static or non-static data member, or even an alias to some type that just happens to live in the T's scope.


Could you give any ideas to do what I want?

Knowing how to check for function existence, you could define views as variable templates, grouping general predicates, e.g.:

template <typename T>
inline constexpr bool ViewAB = HasAFunc<T> && HasBFunc<T>;

With that, you just check:

if constexpr (ViewAB<std::decay_t<decltype(obj)>>)

Another solution is to use the detection idiom form Library fundamentals v2:

template <typename T>
using ViewAB = decltype(std::declval<T>().a(3), std::declval<T>().b(3.14f));

template <typename T>
using ViewC = decltype(std::declval<T>().c(2.7271));

And use it like:

if constexpr (std::experimental::is_detected_v<ViewAB, decltype(obj)>)

DEMO


Alternatively, in , you could define views as concepts:

template <typename T>
concept ViewAB = requires (T t)
{
    t.a(1);
    t.b(3.14f);
};

Not only it makes the code easier to read and clearly presents requirements with sample usage, but also produces an error message explaining which constraint is not satisfied.

DEMO 2

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160