Как гарантировать согласованность интерфейсов между специализациями классов шаблонов C ++? - PullRequest
5 голосов
/ 20 сентября 2019

В: Существуют ли какие-либо механизмы времени компиляции в C ++, которые я могу использовать для автоматической проверки соответствия набора методов класса шаблона от специализации класса к специализации?

Пример: Позвольте намПредположим, я хочу интерфейс класса, который имеет очень различное поведение в зависимости от специализации значения шаблона:

// 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);}
};

Идея заключается в том, что при компиляции по вторникам MyClass.GetValue () возвращает 0, тогда как в любой другой деньнеделю мы получаем ожидаемое значение ... или что-то подобное.Детали и мотивы не важны.; -)

Теперь, хотя это кажется одним из способов достижения частичной специализации функции, основанной на частичной специализации класса, также кажется, что это также может быть полный хаос, потому что две специализации класса могут (AFAIK) иметь совершенно несовместимыеинтерфейсы.Если я на самом деле собираюсь использовать этот механизм в производственной среде, я хотел бы получить уведомление о времени идеальной компиляции, если я по ошибке добавлю некоторые методы к некоторым специализациям, а не другим, или где-нибудь забуду const и т. Д. Как мне это получить?

Что касается бонусных баллов, давайте предположим, что я не хочу, чтобы случайно позволить другим значениям isZero, кроме (true / false), скомпилироваться, как это может произойти, если я предоставлю универсальную реализацию MyClass и допустим такжеЯ с подозрением отношусь к дополнительным затратам времени исполнения от добавления виртуального здесь для чисто виртуальных методов базового класса.

Это кажется настолько очевидной языковой особенностью, что мне, вероятно, не хватает леса для деревьев здесь, и, возможно, яЯ уже получаю это поведение через какой-то механизм и еще не осознал этого.

> 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

Ответы [ 2 ]

2 голосов
/ 20 сентября 2019

Вы можете сделать статическое утверждение в точке использования:

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>);

    ... 

};

Вы также можете попытаться определить класс признаков, который определяет / идентифицирует интерфейс, если вы обнаружите, что делаете это утверждение в нескольких местах..

Поскольку вы оставляете параметр шаблона неспециализированным (а именно T), черты немного неловкие, но они могут работать:

// 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>);
1 голос
/ 20 сентября 2019

Возможно, юнит-тесты?

С Catch2 вы можете использовать TEMPLATE_TEST_CASE_SIG для компиляции одного и того же теста для разных специализаций.Вы можете выразить сходство между специализациями, используя «фактический код» вместо черт.

Это может выглядеть примерно так:

TEMPLATE_TEST_CASE_SIG(
  "MyClass specialization compat", 
  "[myclass]", 
  ((typename T, bool isZero), T, isZero),
  (int, false), (int, true)
) {
  const MyClass<T, isZero> mc{T{}};
  T val{mc.GetValue()};
}

Это гарантирует, что GetValue существует, помечено const и возвращает то, что можно сузить до T.Вам не нужно использовать фреймворк для юнит-тестирования.Вы можете просто использовать шаблон функции.Вам не нужно на самом деле запускать тесты.Вам просто нужно скомпилировать их.

template <typename T, bool isZero>
void test() {
  const MyClass<T, isZero> mc{T{}};
  T val = mc.GetValue();
}

inline void runTests() {
  test<int, false>();
  test<int, true>();
}

Если вы хотите быть более тщательным, вы получите ту же скуку, что и в ответе Николаса.

...