0

Is there a way in which I can test that the Derived class is derived from BaseInterface where the template parameters for the BaseInterface are unsigned integral values? I'm wanting to perform this check as part of a template std::enable_if. The BaseInterface has a method that takes in a reference to a block of bytes, of which that block will have a fixed size depending upon the OutputSize template parameter.

template < size_t BlockSize, size_t OutputSize >
class BaseInterface {
public:
    const static size_t BLOCK_SIZE = BlockSize;
    const static size_t OUTPUT_SIZE = OutputSize;
    using Output = uint8_t[ OutputSize ];
    virtual ~BaseInterface() = default;
    virtual void update( uint8_t*, size_t ) = 0;
    virtual void getOutput( Output& ) = 0;
};

class Derived : public BaseInterface< 64, 16 > {
public:
    ...
};

I've seen a comment mentioning that this could be done, but it would be more complicated than the usual std::is_base_of or std::derived_from. Of course, I've not been able to find any instance of this "more complicated way".
Because the template parameters are not types, but instead unsigned integral values, neither of these work. I guess what I'm mostly interested in is whether the interface is present in the class passed in via the template. I could always just check for every method, typedef and constant. That might get annoying... So if there's a better way of doing this feel free to speak up.

template < typename ImplementedInterface,
    typename = typename std::enable_if<
            ... other checks here ...
            is_derived< Derived, Base ??? >
        >::type >
void method( ... input here ... ) {
    ImplementedInterface ii;
    ImplementedInterface::Output out;
    ii.update( ... );
    ii.output( out );
}

In the mean time, I'll be figuring out how much work it would be to check the derived class for the required interface.

2 Answers2

0

It's actually easy to detect the base class template, provided that it is unambiguous and accessible, for example:

template <size_t BlockSize, size_t OutputSize>
size_t get_block_size(const BaseInterface<BlockSize, OutputSize>&) {
    return BlockSize;
}

get_block_size(Derived{});  // returns 64

This is because while template argument deduction normally requires an "exact match", there is an exception to this rule when the argument type is of a derived class type. In this case, if template argument deduction can't succeed with the derived class type, the compiler tries with the base class of the argument type (proceeding up the inheritance hierarchy, if necessary, until deduction succeeds). 1

You can use this feature to construct your desired type trait easily.

1 This rule applies only when the parameter type is a simple-template-id or pointer to simple-template-id; C++20 [temp.deduct.call]/4.3.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • Thank you a lot. This simplifies the confusing mess I've been hacking together. – Brent Weichel Jan 08 '21 at 00:19
  • This seems strangely procedural to me, compared to the OP's code. Couldn't you just make `get_block_size` a `const` member function of `BaseInterface`, or am I missing something obvious here? (obviously it would then be inappropriate to call it an "interface", but that's besides the point) – Alexander Guyer Jan 08 '21 at 00:36
  • @Nerdizzle You're right, this is not how one would normally implement `get_block_size`. I am only using it to illustrate the principle that the OP can use to detect whether a type is derived from a specialization of a given template. – Brian Bi Jan 08 '21 at 00:49
0

Disclaimer: This answer is largely based off of @Jarod42's answer to another question.

While @Brian's answer might work for your particular case, in cases where you need to do more complicated things with the derived type (as in, it's not an option to up-cast it to BaseType<64, 16>, perhaps because you need to maintain type consistency somewhere), then here's a more general solution:

template<typename T>
struct is_derived_from_base_interface {
private:
    template<size_t S1, size_t S2>
    static decltype(static_cast<const BaseInterface<S1, S2>&>(std::declval<T>()), std::true_type{})
        test(const BaseInterface<S1, S2>&);
    static std::false_type test(...);
public:
    static constexpr bool value =
        decltype(is_derived_from_base_interface::test(std::declval<T>()))::value;
};

int main() {
    // prints 1, for true
    std::cout << "Derived is derived from BaseInterface: " << is_derived_from_base_interface<Derived>::value << "\n";
    // prints 0, for false
    std::cout << "Derived is derived from BaseInterface: " << is_derived_from_base_interface<std::string>::value << "\n";
    return 0;
}

You could use it with std::enable_if like so:

template<typename T>
typename std::enable_if<is_derived_from_base_interface<T>::value>::type my_function(T&& arg) {
    // Do something
}
Alexander Guyer
  • 2,063
  • 1
  • 14
  • 20