Как создать класс шаблона, который абстрагирует типы данных?

Я пишу набор алгоритмов для создания простой структуры модульного тестирования. Класс с названием UnitTest создается строкой символов с названием strng, которая описывает проводимый тест. Типы данных также передаются при создании экземпляра объекта, что позволяет компилятору узнать, какие типы данных передаются. Файл main.cpp и файл unit_test.hpp показаны ниже.

// main.cpp file
#include <iostream>
#include <string>
#include <vector>
#include <array>
#include "unit_test.hpp"

int main(int argc, const char * argv[]) {
    std::vector<int> array_one = {1, 2, 3, 4};
    std::vector<float> array_two = {0.99, 1.99, 2.99, 3.99};

    std::string c ("Vector Test");
    UnitTest<int, float> q(c);
    double unc = 0.1;
    q.vectors_are_close(array_two, array_four, unc);
    return 0;
}

// unit_test.hpp file
#ifndef unit_test_hpp
#define unit_test_hpp
#endif /* unit_test_hpp */
#include <string>
#include <typeinfo>
#include <iostream>
#include <cmath>

template <class type1, class type2> class UnitTest
{
public:
    unsigned long remain;
    std::string str;
    UnitTest(std::string strng) {
        str = strng;
        remain = 50 - str.length();
    };
    void vectors_are_close(std::vector<type1>& i, std::vector<type2>& j, double k);
    // ----------------------------------------------------------------
private:
    void is_close(type1& i, type2& j, double k);
};

template <class type1, class type2> void UnitTest<type1, type2>::
vectors_are_close(std::vector<type1>& i, std::vector<type2>& j, double k)
{
    if (i.size() != j.size()) 
    {
        std::cout << str + std::string(remain, '.') +
            std::string("FAILED") << std::endl;
    }
    else 
    {
        try
        {
            for (int a = 0; a < i.size(); a++) {
                is_close(i[a], j[a], k);
            }
            std::cout << str + std::string(remain, '.') +
                std::string("PASSED") << std::endl;
        }
        catch (const char* msg)
        {
            std::cout << str + std::string(remain, '.') +
                std::string("FAILED") << std::endl;
        }
    }
}

template <class type1, class type2> void UnitTest<type1, type2>::
is_close(type1& i, type2& j, double k)
{
    double percent_diff = abs((j - i) / ((i + j) / 2.0));
    if (percent_diff > k)
    {
        throw "Number not in Tolerance";
    }
}

Показанная выше функция-член выполняет итерацию по векторным контейнерам, чтобы гарантировать, что данные в каждом induce соответствуют второму вектору в пределах определенного допуска. Хотя написанный код работает просто отлично, он требует от пользователя повторного создания экземпляра класса каждый раз, когда он хочет провести модульный тест с другим типом данных.

В этом случае класс создается с помощью UnitTest<int, float>. Но в другом случае он может быть создан с помощью UnitTest<float, double>.

В этом подходе нет ничего плохого, но было бы более элегантно просто создать экземпляр класса один раз с чем-то вроде UnitTest<> и просто заставить функцию vectors_are_close() принимать разные типы данных. Есть ли способ облегчить это поведение?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
99
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если я правильно понял, вы не хотите создавать экземпляр класса с аргументами шаблона, а просто с именем класса UnitTest и хотите передавать разные экземпляры функций-членов в соответствии с разными type1s и type2s.

Если это так, вам не нужен класс шаблона, шаблонные функции-члены:

class UnitTest 
{
private:
    std::string str;
    unsigned long remain;
public:
    UnitTest(const std::string& strng)
        : str{ strng },
          remain{ 50 - str.size() }
    {}

    template <class type1, class type2>
    void vectors_are_close(const std::vector<type1> &i, const std::vector<type2> &j, double k)
    {
       // code
    }
private:
    template <class type1, class type2>
    void is_close(type1 i, type2 j, double k)
    {
      // code
    }
};

int main() 
{
    std::vector<int> array_one{ 1, 2, 3, 4 };
    std::vector<float> array_two{ 0.99f, 1.99f, 2.99f, 3.99f };
    std::vector<double> array_three{ 0.99, 1.99, 2.99, 3.99 };
    double unc = 0.1;

    UnitTest q{ std::string{"Vector Test"} }; // non-templated class
    // call different types of args to same UnitTest obj
    q.vectors_are_close(array_one, array_two, unc);
    q.vectors_are_close(array_one, array_three, unc);
    return 0;
}

Примечание: если вы хотите создавать экземпляры функций-членов только для целых чисел и чисел с плавающей запятой (или любой специальной группы типов), используйте вместе с ними СФИНАЭ.

Например, использование трейтов is_oky_types позволит вам создавать экземпляры функций-членов только для тех арифметических типов, которые допустимы для тела функции.

#include <type_traits>

template<typename Type>
using is_oky_type = std::conjunction<
    std::is_arithmetic<Type>,
    std::negation<std::is_same<Type, bool>>,
    std::negation<std::is_same<Type, char>>,
    std::negation<std::is_same<Type, char16_t>>,
    std::negation<std::is_same<Type, char32_t>>,
    std::negation<std::is_same<Type, wchar_t>> >;

template<typename T, typename U>
using is_oky_types = std::conjunction<is_oky_type<T>, is_oky_type<U>>;

и в функциях-членах:

template <
    class type1,
    class type2,
    std::enable_if_t<is_oky_types<type1, type2>::value>* = nullptr
>
void  vectors_are_close(const std::vector<type1> &i, const std::vector<type2> &j, double k)
{
    // code...
}

template <class type1, class type2>
void is_close(
    type1 i,
    type2 j,
    double k,
    std::enable_if_t<is_oky_types<type1, type2>::value>* = nullptr)
{
    // code...
}

Спасибо, по какой-то причине я не думал, что вы можете создать экземпляр функции шаблона в классе, не являющемся шаблоном, но теперь я вижу, что это произошло потому, что я не объявлял типы классов в функции, а делал это только в прототипе. Вскоре я задам еще один связанный с этим вопрос.

Jon 23.04.2019 15:39

Другие вопросы по теме