Как упростить псевдоним enable_if в параметре шаблона - PullRequest
0 голосов
/ 01 октября 2019

Моя цель состоит в том, чтобы иметь структуру, которая берет псевдоним специализированного enable_if_t<> вместе с набором параметров типа typename и затем сообщает мне, были ли выполнены условия enable_if для всех типов в пакете,У меня есть несколько этих специализированных enable_if, но мне нужно написать тесты для них, прежде чем мы сможем поместить их в наш проект с открытым исходным кодом. У меня есть около 2000+ строк кода, вручную тестирующих эти специализации, но держу пари, что я могу довести его до 100 или 200, если смогу разобраться в схеме ниже. У меня есть рабочая версия (+ ссылка Godbolt), но я не уверен, почему это работает, и эта схема не работает в случае, когда реализация получает пакет параметров

Вот пример кода, который яхотел бы написать и это результат. Я использую C ++ 14 и могу украсть базовые реализации вещей из C ++ 17, например, conunction и void_t

#include <type_traits>
#include <string>

// enable_if for arithmetic types
template <typename T>
using require_arithmetic = typename std::enable_if_t<std::is_arithmetic<T>::value>;

const bool true_arithmetic = require_tester<require_arithmetic, double, int, float>::value;
// output: true

// If any of the types fail the enable_if the result is false
const bool false_arithmetic = require_tester<require_arithmetic, double, std::string, float>::value;
// output: false

Ниже приведено то, что я хочу, но я не совсем понимаю, как,

// Base impl
 template <template <class> class Check, typename T1, typename = void>
 struct require_tester_impl : std::false_type {};

 // I'm not totally sure why void_t needs to be here?
 template <template <class> class Check, typename T1> 
 struct require_tester_impl<Check, T1, void_t<Check<T1>>> : std::true_type {};

 // The recursive version (stolen conjuction from C++17)
 template <template <class> class Check, typename T = void, typename... Types>
 struct require_tester {
  static const bool value = conjunction<require_tester_impl<Check, T>,
   require_tester<Check, Types...>>::value;
 };

// For the end
 template <template <class> class Check>
 struct require_tester<Check, void> : std::true_type {} ;

В частности, я не уверен, почему void_t необходим для частичной специализации impl для std::true_type.

Что я хотел бы получить, это require_variadic_tester, который принимает альтернативный шаблонный псевдоним, что-то вроде enable_if<conjunction<check<T...>>::value>, и дает мне истину или ложь. К сожалению, ниже возвращается false независимо от того, какие типы входят


// impl
template <template <class...> class Check, typename... Types>
struct require_variadic_impl : std::false_type {};

// Adding void_t here causes the compiler to not understand the partial specialiation
template <template <class...> class Check, typename... Types>
struct require_variadic_impl<Check, Check<Types...>> : std::true_type {};

template <template <class...> class Check, typename... Types>
struct require_variadic_tester : require_variadic_impl<Check, Types...> {};

Я хотел бы получить следующее, учитывая входные данные, но, похоже, не может пошатнуться, как скрыть это соединение на один уровень ниже

// Enable if for checking if all types are arithmetic
template <typename... Types>
using require_all_arithmetic = std::enable_if_t<conjunction<std::is_arithmetic<Types>...>::value>;

require_variadic_tester<require_all_arithmetic, double, double, double>::value;
// is true

require_variadic_tester<require_all_arithmetic, double, std::string, double>::value;
// is false

Я думаю, что моя неспособность понять void_t в первой мета-функции вызывает моё недоразумение

Ниже, черт возьми, любая помощь в понимании этого очень ценится!

https://godbolt.org/z/8XNqpo

Редактировать:

Чтобы дать больше контекста, почему я хочу выше с соединением внутри enable_if_t. Я застрял на C ++ 14, но мы добавляем новую функцию в нашу математическую библиотеку с открытым исходным кодом, которая без более общих типов (и требований к этим универсальным типам) приведет к огромному количеству кода. В настоящее время у нас есть что-то вроде этого


template <int R, int C>
inline Eigen::Matrix<double, R, C> add(
    const Eigen::Matrix<double, R, C>& m1, const Eigen::Matrix<double, R, C>& m2) {
  return m1 + m2;
}

Я хотел бы иметь более общие шаблоны и сделать что-то вроде этого


template <typename Mat1, typename Mat2,
  require_all_eigen<is_arithmetic, Mat1, Mat2>...>
inline auto add(Mat1&& m1, Mat2&& m2) {
  return m1 + m2;
}

У меня есть все эти настройки require_*_<container> aliases, нотесты для всех этих требований - около 2000+ строк, и в будущем это будет сложный беспорядок, с которым придется иметь дело.

У нас есть унарный и переменный шаблон enable_if aliases, на данный момент унарный случай вышевыполняет то, что я хочу, а также хороший тест, например

#include <gtest/gtest.h>

TEST(requires, arithmetic_test) {
  EXPECT_FALSE((require_tester<require_arithmetic, std::string>::value));
  EXPECT_TRUE((require_tester<require_arithmetic, double, int, float>::value));
}

Проблема, с которой я столкнулся, заключается в тестировании variadic template enable_if aliases, где я хочу иметь возможность написать что-то вроде


// Enable if for checking if all types are arithmetic 
template <typename... Types>
using require_all_arithmetic = std::enable_if_t<conjunction<std::is_arithmetic<Types>...>::value>;
/// For the tests

TEST(requires, arithmetic_all_test) {
  EXPECT_FALSE((require_variadic_tester<require_all_arithmetic, std::string,
     Eigen::Matrix<float, -1, -1>>::value));
  EXPECT_TRUE((require_variadic_tester<require_all_arithmetic,
     double, int, float>::value));
}

Если я смогу протестировать все это, я думаю, что requires часть нашей библиотеки могла бы быть просто мини-библиотекой с хорошим заголовком для того, что я называю «плохими ложными концепциями в 14» (или для краткости bfc14; -))

Ответы [ 3 ]

1 голос
/ 01 октября 2019

Вот что происходит с вашим require_tester<require_arithmetic, double, double, int>:

Это не соответствует частичной специализации require_tester, которая имеет только два аргумента шаблона <Check, void>, поэтому мы используем основной шаблон

template <template <class> class Check, typename T, typename... Types>
struct require_tester;

с Check = require_arithmetic;T = double;Types = double, int. Не соответствует частичной специализации require_tester. Элемент value является результатом

conjunction<require_tester_impl<Check, T>, require_tester<Check, Types...>>::value

, где интересная часть - require_tester_impl<Check, T> = require_tester_impl<require_arithmetic, double>. Во-первых, поскольку параметры шаблона require_tester_impl равны

template <template <class> class Check, typename T1, typename = void>

и даны только два явных аргумента шаблона, мы знаем, что фактические аргументы шаблона равны <require_arithmetic, double, void>. Теперь нам нужно выяснить, соответствует ли это частичной специализации require_template_impl, поэтому мы попытаемся сопоставить:

require_template_impl<require_arithmetic, double, void>
require_template_impl<Check, T1, void_t<Check<T1>>>

Таким образом, при выводе аргумента шаблона будут найдены Check = require_arithmetic и T1 = double. Тип void_t<Check<T1>> не приводит к вычету Check или T1. Но выведенные значения параметров должны быть подставлены, и мы находим void_t<Check<T1>> это void_t<require_arithmetic<double>> это void. Это соответствует void из аргументов шаблона, поэтому частичная специализация совпадает, и require_template_impl<require_arithmetic, double, void> наследует std::true_type, а не std::false_type.

С другой стороны, если T1 были std::string вместо double, при замене выведенных аргументов шаблона в значение void_t<require_arithmetic<std::string>> будет недопустимым, из-за возможного enable_if< ... >::type, где нет члена type. Если замена выведенных аргументов шаблона в другие параметры шаблона не удалась, это означает, что частичная специализация отбрасывается как несоответствие. Таким образом, require_template_impl<require_arithmetic, std::string, void> использует основной шаблон и наследует std::false_type.

Возвращаясь к члену value require_tester, он рекурсивно находит require_tester<require_arithmetic, double, int>::value через require_tester<require_arithmetic, int>::value через require_tester<require_arithmetic>::value, который являетсятакой же как require_tester<require_arithmetic, void>::value. Все члены value верны, поэтому окончательный value верен.

Хотя я бы немного упростил это:

  1. void не требуетсяв рекурсии require_tester, и вызывает странный «факт», что require_tester<Anything, void>::value всегда верно. Было бы лучше удалить значение = void по умолчанию из основного шаблона require_tester и вместо него сделать базовый регистр template <template <class> class Check> require_tester<Check>.

  2. Ваше выражение value в require_tester Первичный шаблон всегда дает ровно два аргумента шаблона для conjunction, поэтому он на самом деле не использует его свойство variadic, и вы также можете написать require_tester_impl< ... >::value && require_tester< ... >::value. Поскольку require_tester выполняет рекурсию сама, ей не нужно рекурсивное определение, абстрагированное в conjunction. Вместо этого require_tester можно упростить, чтобы рассчитывать на conjunction и избегать выполнения какой-либо рекурсии:

    template <template <class> class Check, typename... Types>
    struct require_tester : conjunction<require_tester_impl<Check, Types>...>
    {};
    // No other specialization needed.
    

Шаблон require_variadic_tester может следовать аналогичной схеме, за исключением того, что яЯ дам фиктивный параметр шаблона, который был просто typename = void имя, typename Enable. И он должен предшествовать пакету параметров шаблона, так что на самом деле не очень полезно по умолчанию установить его на void, и мы должны убедиться, что используется соответствующий void аргумент шаблона в соответствующей позиции.

template <template <class...> class Check, typename Enable, typename... Types>
struct require_variadic_impl : std::false_type {};

template <template <class...> class Check, typename... Types>
struct require_variadic_impl<Check, void_t<Check<Types...>>, Types...> : std::true_type {};

template <template <class...> class Check, typename... Types>
struct require_variadic_tester : require_variadic_impl<Check, void, Types...> {};

См. измененную программу на Годболте , с желаемыми результатами.

0 голосов
/ 01 октября 2019

Не уверен, что пойму все ваши потребности, но ...

Я бы хотел получить require_variadic_tester, который принимает вариационный шаблонный псевдоним, что-то вроде enable_if<conjunction<check<T...>>::value>, идает мне правду или ложь. К сожалению, ниже возвращается false независимо от того, какие типы входят

Вы уверены, что хотите conjunction<check<T...>>?

Или хотите conjunction<check<T>...>?

Я имею в виду ... проверка должна получить список переменных типа, или вы хотите проверить псевдоним, который (как в вашем примере) получает один тип и соединение, которое истинно, если (если и только если)проверка выполняется для всех типов?

Во втором случае std::void_t очень удобно для проверки того, что все проверки выполнены.

Я предлагаю следующие require_variadic_impl и require_variadic_tester

template <template <typename> class, typename, typename = void>
struct require_variadic_impl
   : public std::false_type 
 { };

template <template <typename> class C, typename ... Ts>
struct require_variadic_impl<C, std::tuple<Ts...>, std::void_t<C<Ts>...>>
   : public std::true_type
 { };

template <template <typename> class C, typename ... Ts>
struct require_variadic_tester
   : public require_variadic_impl<C, std::tuple<Ts...>>
 { };

Теперь из

template <typename T>
using require_arithmetic = typename std::enable_if_t<std::is_arithmetic<T>::value>;

// ...

printf("\nGeneric Variadic: \n\n");
const char* string_generic_var_check = 
  require_variadic_tester<require_arithmetic, std::string>::value ? "true" : "false";
const char* double_generic_var_check = 
  require_variadic_tester<require_arithmetic, double, double, double>::value ? "true" : "false";
std::printf("\t String: %s\n", string_generic_var_check);
std::printf("\t Double: %s\n", double_generic_var_check);

вы получаете

Generic Variadic: 

     String: false
     Double: true

думаю, что моя неспособность понять void_t в первой мета-функции вызывает мое недоразумение

Попробуйте подумать std::void_t<Ts...> как «включить, если все Ts включены».

0 голосов
/ 01 октября 2019
template <template <class> class Check, typename T1, typename = void>
struct require_tester_impl : std::false_type {};

// I'm not totally sure why void_t needs to be here?
template <template <class> class Check, typename T1> 
struct require_tester_impl<Check, T1, void_t<Check<T1>>> : std::true_type {};

Здесь вам требуется третий параметр require_tester_impl типа void, поскольку вы записали его как значение по умолчанию. Если пользователь при специализации require_tester_impl не указывает свой третий параметр, это void. Таким образом, компилятор будет искать частичную специализацию, где первый параметр шаблона является унарным шаблоном класса, второй параметр шаблона является типом, а третий является void, в противном случае частичная специализация не будет найдена, поскольку третий параметрлюбая частичная специализация потерпит неудачу.

Вот где void_t вступает в игру. Поскольку вы хотите ввести Check в параметр, но вам требуется void, именно тогда void_t пригодится, так как каждый тип, используемый для специализированных, сопоставляется с void, что вам действительно нужно. Если частичная специализация не завершится неудачей, у вас будет две активированные специализации: по умолчанию и частичная.

Частичная специализация будет окончательно выбрана, поскольку она более специализирована, чем другая, поскольку void были рассчитаны способом, зависящим от других параметров шаблона.

Это для первой части. Что касается второй части (шаблон переменной), помните, что если enable_if успешно, он возвращает void.

Таким образом, у вашего require_variadic_impl:

template <template <class...> class Check, typename... Types>
struct require_variadic_impl : std::false_type {};

// Adding void_t here causes the compiler to not understand the partial specialiation
template <template <class...> class Check, typename... Types>
struct require_variadic_impl<Check, Check<Types...>> : std::true_type {};

есть проблема здесьи это то, что Check<Types...>, так как он имеет псевдоним enable_if, возвращает void при успешном выполнении, однако второй параметр require_variadic_impl не равен void, поэтому частичные специализации в конце концов завершаются неудачно, когда проверкаверный. Если это не так, тогда у enable_if не определен внутренний тип, частичная специализация также завершается неудачно, и базовый случай используется снова.

Однако сделайте это просто. Здесь я предлагаю гораздо более читаемую реализацию с тем же конечным результатом:

#include <iostream>
#include <type_traits>
#include <string>

template<class... Ts>
struct require_all_arithmetic : std::conjunction<std::is_arithmetic<Ts>...>
{};

template<template<class...> class Check, class... Ts>
struct require_variadic_tester : Check<Ts...>
{};

int main()
{
    std::cout << require_variadic_tester<require_all_arithmetic, double, double, double>::value << std::endl;
    std::cout << require_variadic_tester<require_all_arithmetic, double, std::string, double>::value << std::endl;
}

https://coliru.stacked -crooked.com / a / f9fb68e04eb0ad40

Или просто:

#include <iostream>
#include <type_traits>
#include <string>

template<class... Ts>
struct require_all_arithmetic : std::conjunction<std::is_arithmetic<Ts>...>
{};

int main()
{
    std::cout << require_all_arithmetic<double, double, double>::value << std::endl;
    std::cout << require_all_arithmetic<double, std::string, double>::value << std::endl;
}

Однако, если вам требуется проверка, дружественная к sfinae, плюс структура, которая отображает проверки, дружественные к sfinae, к true / false, вы можете вместо этого использовать constexpr методы. Это намного проще:

template<class... Ts>
using require_all_arithmetic = std::enable_if_t<std::conjunction<std::is_arithmetic<Ts>...>::value>;

template<template<class...> class Check, class... Ts, class = Check<Ts...> >
constexpr bool require_variadic_tester_impl(int)
{ return true; }

template<template<class...> class Check, class... Ts>
constexpr bool require_variadic_tester_impl(unsigned)
{ return false; }

template<template<class...> class Check, class... Ts>
struct require_variadic_tester
{ static constexpr bool value = require_variadic_tester_impl<Check, Ts...>(42); };

int main()
{
    std::cout << require_variadic_tester<require_all_arithmetic, double, double, double>::value << std::endl;
    std::cout << require_variadic_tester<require_all_arithmetic, double, std::string, double>::value << std::endl;
}

Техника работает следующим образом: если Check не удается, компилируется только вторая перегрузка, которая возвращает false. Однако, если проверка действительна и внутренняя enable_if определена, то обе перегрузки будут действительны, но, поскольку вы передали int (42), а вторая перегрузка получает unsigned, перваяПерегрузка будет лучше соответствовать, возвращая true.

https://coliru.stacked -crooked.com / a / bfe22ea099dd5749

Наконец, если вы хотите, проверка всегдаtrue_type или false_type, тогда вместо наследования вы можете просто использовать псевдоним std::conditional:


template<template<class...> class Check, class... Ts>
using require_variadic_tester = 
   std::conditional_t<require_variadic_tester_impl<Check, Ts...>(42),
                      std::true_type, std::false_type>;

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...