英文:
preconditions for properties of a std::span that could be static or dynamic
问题
I have a function that is generic over the extent of a std::span it receives as a parameter. I want to do some checks on it as efficiently as possible while avoiding duplication but guaranteeing a compile error when it's possible.
My goal is to:
- Error as early as possible, always at compile time when possible.
- Generate a good error message at compile time (static assert failure with file and line number), and at runtime in debug builds (assertion error with file and line number), but have the minimal required overhead in release builds.
- Have zero runtime overhead if checked at compile time. If the runtime check is included it should have an optimization hint in release configuration.
For example, I have the following function that does preflight checks on a std::span
before passing its raw contents to a fast implementation that does no checks. Since it's templated on span's Extent parameter I can sometimes do these checks at compile time. The problem is that leads me to repeat the precondition pairs.size() > 0 && pairs.size() % 2 == 0
three times, depending on whether I am using static_assert
, assert
, or a cheap release mode runtime check:
#include <span>
#ifndef NDEBUG
#include <cassert>
#endif
extern long SumOfDistancesFast(const long *, size_t) noexcept;
template<size_t Extent = std::dynamic_extent>
[[nodiscard]]
constexpr long SumOfDistances(std::span<const long, Extent> pairs) noexcept
{
static_assert(Extent == decltype(pairs)::extent);
if constexpr (Extent != std::dynamic_extent) {
static_assert(pairs.size() == Extent);
static_assert(pairs.size() > 0 && pairs.size() % 2 == 0);
} else {
#ifdef NDEBUG
if (!(pairs.size() > 0 && pairs.size() % 2 == 0)) {
[[unlikely]] __builtin_trap();
}
#else
assert(pairs.size() > 0 && pairs.size() % 2 == 0);
#endif
}
return SumOfDistancesFast(pairs.data(), pairs.size());
}
英文:
I have a function that is generic over the extent of a std::span it receives as a parameter. I want to do some checks on it as efficiently as possible while avoiding duplication but guaranteeing a compile error when it's possible.
My goal is to:
- Error as early as possible, always at compile time when possible.
- Generate a good error message at compile time (static assert failure with file and line number), and at runtime in debug builds (assertion error with file and line number), but have the minimal required overhead in release builds.
- Have zero runtime overhead if checked at compile time. If the runtime check is included it should have an optimization hint in release configuration.
For example, I have the following function that does preflight checks on a std::span
before passing its raw contents to a fast implementation that does no checks. Since it's templated on span's Extent parameter I can sometimes do these checks at compile time. The problem is that leads me to repeat the precondition pairs.size() > 0 && pairs.size() % 2 == 0
three times, depending on whether I am using static_assert
, assert
, or a cheap release mode runtime check:
#include <span>
#ifndef NDEBUG
#include <cassert>
#endif
extern long SumOfDistancesFast(const long *, size_t) noexcept;
template<size_t Extent = std::dynamic_extent>
[[nodiscard]]
constexpr long SumOfDistances(std::span<const long, Extent> pairs) noexcept
{
static_assert(Extent == decltype(pairs)::extent);
if constexpr (Extent != std::dynamic_extent) {
static_assert(pairs.size() == Extent);
static_assert(pairs.size() > 0 && pairs.size() % 2 == 0);
} else {
#ifdef NDEBUG
if (!(pairs.size() > 0 && pairs.size() % 2 == 0)) {
[[unlikely]] __builtin_trap();
}
#else
assert(pairs.size() > 0 && pairs.size() % 2 == 0);
#endif
}
return SumOfDistancesFast(pairs.data(), pairs.size());
}
(Godbolt link with some example usages)
I think I should be able to make this example less repetitive while preserving the same safety properties, something like this:
#include <span>
#define precondition(_cond) /* static or dynamic assert */
extern long SumOfDistancesFast(const long *, size_t) noexcept;
template<size_t Extent = std::dynamic_extent>
[[nodiscard]]
constexpr long SumOfDistances(std::span<const long, Extent> pairs) noexcept
{
precondition(Extent == decltype(pairs)::extent);
precondition(Extent == std::dynamic_extent || pairs.size() == Extent);
precondition(pairs.size() > 0 && pairs.size() % 2 == 0);
return SumOfDistancesFast(pairs.data(), pairs.size());
}
In this updated example, the precondition
macro doesn't care whether I'm statically or dynamically asserting and it will return me an error as soon as possible. Is it possible to write it?
答案1
得分: 1
precondition
宏可以定义为
#define precondition(cond) \
if constexpr (requires { typename std::bool_constant<(cond)>; }) \
static_assert(cond); \
else \
assert(cond);
如果条件是一个常量表达式,可以用作非类型模板参数,则使用 static_assert
。否则,使用运行时的 assert
检查。
template<size_t Extent = std::dynamic_extent>
[[nodiscard]]
constexpr long SumOfDistances(std::span<const long, Extent> pairs) noexcept
{
// 使用这个:
precondition(Extent == decltype(pairs)::extent);
precondition(Extent == std::dynamic_extent || pairs.size() == Extent);
precondition(pairs.size() > 0 && pairs.size() % 2 == 0);
return SumOfDistancesFast(pairs.data(), pairs.size());
}
演示(Clang 似乎无法通过 requires
子句来确定常量表达式,这似乎是一个 bug)
英文:
The precondition
macro can be defined as
#define precondition(cond) \
if constexpr (requires { typename std::bool_constant<(cond)>; }) \
static_assert(cond); \
else \
assert(cond);
If the condition is a constant expression, which can be used as a non-type template argument, then use static_assert
. Otherwise, a runtime assert
check is used.
template<size_t Extent = std::dynamic_extent>
[[nodiscard]]
constexpr long SumOfDistances(std::span<const long, Extent> pairs) noexcept
{
// With this:
precondition(Extent == decltype(pairs)::extent);
precondition(Extent == std::dynamic_extent || pairs.size() == Extent);
precondition(pairs.size() > 0 && pairs.size() % 2 == 0);
return SumOfDistancesFast(pairs.data(), pairs.size());
}
Demo (Clang does not seem to be able to determine constant expressions through the requires
-clause, which seems to be a bug)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论