написать библиотеку проверки концепции, но потерпеть неудачу на всех фундаментальных арифметических типах

Ситуация пошла не так, когда я проверял, могут ли базовые арифметические типы, такие как int, и необработанные указатели, такие как int *, быть pre-incrementabl / post-incrementable.

//has_operator_overload_functions.hpp
namespace concept_check {
template <class T>
T create_T();

struct disambiguation_t2 {};
struct disambiguation_t1 {
constexpr operator disambiguation_t2 () const noexcept;
};
}

# define create_has_op_overload_fn_test(fn_name, ret_t_of_op_to_test) \
template <class T>                                          \
auto has_ ## fn_name (disambiguation_t1) -> ret_t_of_op_to_test;     \
template <class T>                                          \
void has_ ## fn_name (disambiguation_t2);                    \
template <class T>                                          \
inline constexpr const bool has_ ## fn_name ## _v = !is_same_v<decltype(has_ ## fn_name <T>(disambiguation_t1{})), void>;

# define create_check_op_overload_fn_return_type_test(fn_name, ret_t_of_op_to_test, result_t) \
create_has_op_overload_fn_test(fn_name, ret_t_of_op_to_test) \
_create_check_op_overload_fn_return_type_test(fn_name, ret_t_of_op_to_test, result_t)

# define _create_check_op_overload_fn_return_type_test(fn_name, ret_t_of_op_to_test, result_t) \
template <class T>          \
constexpr const bool is_ ## fn_name () noexcept {      \

if constexpr(has_ ## fn_name ## _v<T>)                  \
return is_convertible_v< ret_t_of_op_to_test , result_t >; \
else             \
return 0;                                \
}                                                  \
template <class T>                                      \
inline constexpr const bool is_ ## fn_name ## _v = is_ ## fn_name <T>();

namespace concept_check {
create_check_op_overload_fn_return_type_test(pre_incrementable,
decltype(++create_T<T>()), T&)
create_check_op_overload_fn_return_type_test(post_incrementable,
decltype(create_T<T>()++), T)
}//namespace concept_check

//test.hpp
#include <iostream>
#include "has_operator_overload_functions.hpp"using namespace concept_check;
struct A {
A& operator ++ ();
A operator ++ (int);
};
struct B {
B& operator ++ ();
};
struct C {
C operator ++ (int);
};
struct E {};
int main() {
std::cout << std::boolalpha

<< "incrementable\n\n"
<< "int\n"<< is_pre_incrementable_v<int> << std::endl
<< is_post_incrementable_v<int> << std::endl

<< "\nchar\n"<< is_pre_incrementable_v<char> << std::endl
<< is_post_incrementable_v<char> << std::endl

<< "\nfloat\n"<< is_pre_incrementable_v<float> << std::endl
<< is_post_incrementable_v<float> << std::endl

<< "\ndouble\n"<< is_pre_incrementable_v<double> << std::endl
<< is_post_incrementable_v<double> << std::endl

<< "\nchar*\n"<< is_pre_incrementable_v<char*> << std::endl
<< is_post_incrementable_v<char*> << std::endl

<< "\nvoid*\n"<< is_pre_incrementable_v<void*> << std::endl
<< is_post_incrementable_v<void*> << std::endl

<< "\nA\n"<< is_pre_incrementable_v<A> << std::endl
<< is_post_incrementable_v<A> << std::endl

<< "\nB\n"<< is_pre_incrementable_v<B> << std::endl
<< is_post_incrementable_v<B> << std::endl

<< "\nC\n"<< is_pre_incrementable_v<C> << std::endl
<< is_post_incrementable_v<C> << std::endl

<< "\nE\n"<< is_pre_incrementable_v<E> << std::endl
<< is_post_incrementable_v<E> << std::endl

<< "\nbool\n"<< is_pre_incrementable_v<bool> << std::endl
<< is_post_incrementable_v<bool> << std::endl
}

Я скомпилировал это с помощью

clang version 5.0.1-svn324012-1~exp1 (branches/release_50)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/6
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/6.3.0
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/6
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/6.3.0
Selected GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/6.3.0
Candidate multilib: .;@m64
Selected multilib: .;@m64

, с командной строкой args -std = c ++ 17 -I .. и запустил полученный двоичный файл, который выдал следующий вывод:

incrementable

int
false
false

char
false
false

float
false
false

double
false
false

char*
false
false

void*
false
false

A
true
true

B
true
false

C
false
true

E
false
false

bool
false
false

Как вы можете видеть, все основные арифметические типы и необработанные указатели не прошли предварительный / постинкрементный тест.

Можно ли объяснить, почему это произошло и как это исправить?

1

Решение

create_check_op_overload_fn_return_type_test(pre_incrementable, decltype(++create_T<T>()), T&)

Ты используешь decltype(++create_T<T>()) в качестве эффективного ограничения на вашем is_pre_incrementable_v проверять. Для типа, такого как Aэто эквивалентно тестированию, например ++A {} что верно, как отражено в ваших результатах.

Для типа, такого как int Однако это эквивалентно тестированию ++0, который не является допустимым выражением. (И так далее для других не классовых типов.)

Там нет очевидного решения. Вы можете решить ограничиться, например, ++create_T<T&>() вместо этого (это также хорошее время для упоминания std::declval). Но обратите внимание, что это имеет следующие последствия:

template<typename Arg>
auto under_constrained(Arg const& arg)
-> std::enable_if_t<is_pre_incrementable_v<Arg>>
{
++arg;
}

template<typename Arg>
auto over_constrained(Arg& arg)
-> std::enable_if_t<is_pre_incrementable_v<Arg>, std::decay_t<Arg>>
{
auto copy = arg;
++copy;
return copy;
}
  • первый пример недостаточно ограничен, потому что он эффективно проверяет ++mutable_arg дано воображаемое Arg mutable_arg; переменная, но тело работает с неизменяемой переменной (которая, конечно, не будет увеличиваться в большинстве случаев)
  • второй пример чрезмерно ограничен, потому что, например, если вызвано int const immutable_variable = 0; в качестве аргумента он эффективно проверяет ++immutable_arg, но тело действует на int переменная

Теперь этот пример можно настроить так, чтобы вместо этого он был ограничен, например, is_pre_incrementable_v<Arg const&> а также is_pre_incrementable_v<std::decay_t<Arg>&> соответственно. Но это требует осторожности со стороны пользователя концепции. Вот почему исправление не обязательно является подходящим решением.

Все сводится к значение параметров шаблона для данной концепции.

2

Другие решения

Других решений пока нет …