Модульный тест для проверки свойства noexcept для метода C ++ - PullRequest
2 голосов
/ 09 июня 2019

У меня есть несколько методов, которые

  • должны быть отмечены noexcept
  • не должен быть помечен noexcept

Как написать модульные тесты, которые проверяют правильность пометки метода noexcept?

Причина: чтобы гарантировать, что в будущем эти свойства не будут изменены во время рефакторинга другими разработчиками или злой версией меня.

В настоящее время я использую CMake / CTest и добавляю рукописные исполняемые файлы в набор тестов.

Ответы [ 2 ]

5 голосов
/ 09 июня 2019

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

void foo() noexcept { }
void bar() { }

static_assert(noexcept(foo())); // OK
static_assert(noexcept(bar())); // Will fail

Если это функция-член, то:

struct S {
    void foo() noexcept { }
};

static_assert(noexcept(S().foo()));

Нет вызова функции или чего-либо выполненного. Оператор noexcept только проверяет выражение, но не оценивает его.

Чтобы требовать, чтобы функция не была noexcept, просто используйте !noexcept(bar()).

2 голосов
/ 09 июня 2019

Поскольку вы используете C ++ 17, функция noexcept функции является частью ее типа.

Чтобы проверить тип аргументов и тип возвращаемого значения для одного и того же типа, вы можетепросто используйте простое std::is_same:

void foo(int, int) noexcept;
void bar(long, float);
struct S {
    void foo(int, int) noexcept;
    void bar(long, float);
};

static_assert(std::is_same_v<decltype(foo), void(int, int) noexcept>);
static_assert(std::is_same_v<decltype(bar), void(long, float)>);
static_assert(std::is_same_v<decltype(&S::foo), void(S::*)(int, int) noexcept>);
static_assert(std::is_same_v<decltype(&S::bar), void(S::*)(long, float)>);

Вы также можете использовать вычет аргумента шаблона, чтобы увидеть, не является ли тип функции исключением, без проверки noexcept(std::declval<S>().foo(std::declval<arg_1_t>(), std::declval<arg_2_t>())):

// Arguments, return type and type of the class that has the member function are
// all deduced, but this overload is only called if noexcept
template<class RetT, class T, class... Args>
constexpr bool is_noexcept_function(RetT(T::*)(Args...) noexcept) {
    return true;
}
// And this one is called if not noexcept
template<class RetT, class T, class... Args>
constexpr bool is_noexcept_function(RetT(T::*)(Args...)) {
    return false;
}

static_assert(is_noexcept_function(&S::foo));
static_assert(!is_noexcept_function(&S::bar));

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

#include <type_traits>

// Check if a regular function is noexcept
template<class Ret, class... Args>
constexpr std::false_type is_noexcept_function(Ret(Args...)) noexcept {
    return {};
}

template<class Ret, class... Args>
constexpr std::true_type is_noexcept_function(Ret(Args...) noexcept) noexcept {
    return {};
}

// Check if a regular function with C-style variadic arguments is noexcept
template<class Ret, class... Args, bool is_noexcept>
constexpr std::false_type is_noexcept_function(Ret(Args......)) noexcept {
    return {};
}

template<class Ret, class... Args, bool is_noexcept>
constexpr std::true_type is_noexcept_function(Ret(Args......) noexcept) noexcept {
    return {};
}

// Check if a member function is noexcept
#define DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD(QUALIFIER) \
template<class Ret, class T, class... Args> \
constexpr std::false_type is_noexcept_function(Ret(T::*)(Args...) QUALIFIER) noexcept { \
    return {}; \
} \
template<class Ret, class T, class... Args> \
constexpr std::true_type is_noexcept_function(Ret(T::*)(Args...) QUALIFIER noexcept) noexcept { \
    return {}; \
} \
template<class Ret, class T, class... Args, bool is_noexcept> \
constexpr std::false_type is_noexcept_function(Ret(T::*)(Args......) QUALIFIER) noexcept { \
    return {}; \
} \
template<class Ret, class T, class... Args, bool is_noexcept> \
constexpr std::true_type is_noexcept_function(Ret(T::*)(Args......) QUALIFIER noexcept) noexcept { \
    return {}; \
}

#define DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD_VALUE_CLASS(VALUE_CLASS) \
    DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD(VALUE_CLASS) \
    DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD(const VALUE_CLASS) \
    DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD(volatile VALUE_CLASS) \
    DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD(const volatile VALUE_CLASS)

DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD_VALUE_CLASS()
DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD_VALUE_CLASS(&)
DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD_VALUE_CLASS(&&)

#undef DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD
#undef DEFINE_IS_NOEXCEPT_FUNCTION_FOR_METHOD_VALUE_CLASS


// Usage example

void foo(int, int) noexcept;
void bar(long, float);
struct S {
    void foo(int, int) const noexcept;
    void bar(long, float) &&;
};

static_assert(is_noexcept_function(foo));
static_assert(!is_noexcept_function(bar));
static_assert(is_noexcept_function(&S::foo));
static_assert(!is_noexcept_function(&S::bar));

(Большую часть времени вы можете обойтись просто поддержкой RetT(Args...), RetT(T::*)(Args...) и RetT(T::*)(Args...) const, так как вы редко видите дикие функции и функции-члены категории значений в дикой природе)

Это не будет работать с шаблонными или перегруженными функциями / функциями-членами.Это связано с тем, что noexcept-ness может зависеть от параметров шаблона или перегруженных типов аргументов.Вы можете вручную указать аргументы шаблона (например, is_noexcept(add<int>) и !is_noexcept(add<std::string>)) или использовать операторы noexcept и std::declval.

.
...