Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to guarantee consistent interfaces between C++ template class specializations?

Tags:

Q: Are there are any compile time mechanisms in C++ I can use to automatically verify that the set of template class methods matches from class specialization to specialization?

Example: Let us suppose I want a class interface that has very different behaviors depending on a template value specialization:

// forward declaration of template class name
template <typename T, bool isZero> class MyClass;

// partial class specialization for isZero = false case
template <typename T> class MyClass <T, false>
{
   T _val;

public:
   MyClass(T value) { _val = value; }
   T GetValue(void) const { return _val; }
};

// partial class specialization for isZero = true case
template <typename T> class MyClass <T, true>
{
public:
   MyClass(T value) {}
   T GetValue(void) const {return T(0);}
};

The idea here is that when compiling on Tuesdays, MyClass.GetValue() returns 0, whereas any other day of the week, we get the expected value ...or some such thing. The details and motivations are unimportant. ;-)

Now, while this seems like one way to achieve partial function specialization based on partial class specialization, it also seems like it can also be utter chaos because the two class specializations can (AFAIK) have completely inconsistent interfaces. If I am actually going to use this mechanism in a production setting, I would like some ideally compile time notification if I mistakenly add some methods to some specializations and not others or forget a const somewhere, etc. How do I get that?

For bonus points, let us suppose I don't want to accidentally allow other values of isZero beyond (true/false) to compile as might occur if I provided a generic implementation of MyClass and let us also suppose I am suspicious of added runtime cost from adding virtual here for pure virtual base class methods.

This seems like such an obvious language feature that I'm likely missing the forest for the trees here and maybe I'm already getting this behavior through some mechanism and haven't realized it yet.

> cat test.cpp                   
#include <stdio.h>

// forward declaration of template class name
template <typename T, bool isZero> class MyClass;

// partial specialization for isZero = false case
template <typename T> class MyClass <T, false>
{
   T _val;

public:
   MyClass(T value) { _val = value; }
   T GetValue(void) const { return _val; }
};

// partial specialization for isZero = true case
template <typename T> class MyClass <T, true>
{
public:
   MyClass(T value) {}
   T GetValue(void) const {return T(0);}
};

int main( void )
{
   MyClass<float, false>  one(1);
   MyClass<float, true> notOne(1);

   printf( "%f, %f\n", one.GetValue(), notOne.GetValue());
   return 0;
}
> clang -Wall -pedantic test.cpp 
> ./a.out                        
1.000000, 0.000000
like image 950
Ian Ollmann Avatar asked Sep 20 '19 01:09

Ian Ollmann


1 Answers

You could make a static assertion at the point of use:

template <typename T>
class MyOtherClass
{
    static_assert(std::is_same_v<decltype(MyClass<T, true >{T{}}.GetValue()), T>);
    static_assert(std::is_same_v<decltype(MyClass<T, false>{T{}}.GetValue()), T>);

    ... 

};

You could also attempt to define a traits class that defines/identifies the interface, if you find yourself making this assertion in several places.

Because you're leaving a template parameter unspecialized (namely T), the traits are a little awkward, but these might work:

// This traits class will inherit from either 
// std::true_type or std::false_type.

template <template <typename, bool> S, typename T>
struct is_value_provider : std::intergral_constant<bool, 
    std::is_same_v<decltype(S<T, true >{T{}}.getValue()), T> &&
    std::is_same_v<decltype(S<T, false>{T{}}.getValue()), T>>
{}

template <template <typename, bool> S, typename T>
using is_value_provider_v = is_value_provider::value;


// Usage examples:
static_assert(is_value_provider_v<MyClass, int>);
static_assert(is_value_provider_v<MyClass, float>);
static_assert(is_value_provider_v<MyClass, double>);
like image 54
NicholasM Avatar answered Oct 08 '22 17:10

NicholasM