发明一个可区分的联合/标记变体 我的结论是,特别需要诸如"在编译时在某些条件下使析构函数变得微不足道"这样的功能。我的意思是某种 SFINAE 或类似的东西(伪代码):
template< typename ...types >
struct X
{
~X() = default((std::is_trivially_destructible< types >{} && ...))
{
// non-trivial code here
}
};
这意味着如果default(*)
中的条件是true
,那么析构函数的定义等于~X() = default;
,但如果它是false
则使用{ // ... }
主体代替。
#pragma once
#include <type_traits>
#include <utility>
#include <experimental/optional>
#include <cassert>
template< typename ...types >
class U;
template<>
class U<>
{
U() = delete;
U(U &) = delete;
U(U const &) = delete;
U(U &&) = delete;
U(U const &&) = delete;
void operator = (U &) = delete;
void operator = (U const &) = delete;
void operator = (U &&) = delete;
void operator = (U const &&) = delete;
};
template< typename first, typename ...rest >
class U< first, rest... >
{
struct head
{
std::size_t which_;
first value_;
template< typename ...types >
constexpr
head(std::experimental::in_place_t, types &&... _values)
: which_{sizeof...(rest)}
, value_(std::forward< types >(_values)...)
{ ; }
template< typename type >
constexpr
head(type && _value)
: head(std::experimental::in_place, std::forward< type >(_value))
{ ; }
};
using tail = U< rest... >;
union
{
head head_;
tail tail_;
};
template< typename ...types >
constexpr
U(std::true_type, types &&... _values)
: head_(std::forward< types >(_values)...)
{ ; }
template< typename ...types >
constexpr
U(std::false_type, types &&... _values)
: tail_(std::forward< types >(_values)...)
{ ; }
public :
using this_type = first; // place for recursive_wrapper filtering
constexpr
std::size_t
which() const
{
return head_.which_;
}
constexpr
U()
: U(typename std::is_default_constructible< this_type >::type{}, std::experimental::in_place)
{ ; }
U(U &) = delete;
U(U const &) = delete;
U(U &&) = delete;
U(U const &&) = delete;
template< typename type >
constexpr
U(type && _value)
: U(typename std::is_same< this_type, std::decay_t< type > >::type{}, std::forward< type >(_value))
{ ; }
template< typename ...types >
constexpr
U(std::experimental::in_place_t, types &&... _values)
: U(typename std::is_constructible< this_type, types... >::type{}, std::experimental::in_place, std::forward< types >(_values)...)
{ ; }
void operator = (U &) = delete;
void operator = (U const &) = delete;
void operator = (U &&) = delete;
void operator = (U const &&) = delete;
template< typename type >
constexpr
void
operator = (type && _value) &
{
operator std::decay_t< type > & () = std::forward< type >(_value);
}
constexpr
explicit
operator this_type & () &
{
assert(sizeof...(rest) == which());
return head_.value_;
}
constexpr
explicit
operator this_type const & () const &
{
assert(sizeof...(rest) == which());
return head_.value_;
}
constexpr
explicit
operator this_type && () &&
{
assert(sizeof...(rest) == which());
return std::move(head_.value_);
}
constexpr
explicit
operator this_type const && () const &&
{
assert(sizeof...(rest) == which());
return std::move(head_.value_);
}
template< typename type >
constexpr
explicit
operator type & () &
{
return static_cast< type & >(tail_);
}
template< typename type >
constexpr
explicit
operator type const & () const &
{
return static_cast< type const & >(tail_);
}
template< typename type >
constexpr
explicit
operator type && () &&
{
//return static_cast< type && >(std::move(tail_)); // There is known clang++ bug #19917 for static_cast to rvalue reference.
return static_cast< type && >(static_cast< type & >(tail_)); // workaround
}
template< typename type >
constexpr
explicit
operator type const && () const &&
{
//return static_cast< type const && >(std::move(tail_));
return static_cast< type const && >(static_cast< type const & >(tail_));
}
~U()
{
if (which() == sizeof...(rest)) {
head_.~head();
} else {
tail_.~tail();
}
}
};
// main.cpp
#include <cstdlib>
int
main()
{
U< int, double > u{1.0};
assert(static_cast< double >(u) == 1.0);
u = 0.0;
assert(static_cast< double >(u) == 0.0);
U< int, double > w{1};
assert(static_cast< int >(w) == 1);
return EXIT_SUCCESS;
}
在这个使类U
文字类型的例子中(在first, rest...
都是平凡可破坏的情况下),可以定义几乎与类U
相同的类(V
),但没有定义析构函数~U
(即,如果所有降序类型都是文字类型,则为文字类型)。然后定义模板类型别名
template< typename ...types >
using W = std::conditional_t< (std::is_trivially_destructible< types >{} && ...), V< types... >, U< types... > >;
并在U
和V
中重新定义using tail = W< rest... >;
。因此,有两个几乎相同的类,仅在析构函数的存在上有所不同。上述方法需要过多的代码重复。
该问题还涉及简单复制/移动可分配类型和operator =
以及要std::is_trivially_copyable
类型的所有其他条件。5 个条件总共给出了 2^5 个组合来实现。
是否有任何现成的技术(并且不那么冗长,然后如上所述)可以在我现在表达C++我想念,或者可能即将推出的提案?
另一种可行的方法是(语言功能)将析构函数标记为constexpr
并授予编译器在实例化期间测试主体是否等效于普通主体。
更新:
代码简化,如注释中所述: union
变成了类似union
类。 删除了noexcept
说明符。
条件析构函数可以通过具有模板专用化的附加中间层来实现。例如:
科里鲁的现场演示
#include <type_traits>
#include <iostream>
#include <vector>
using namespace std;
template<typename T>
class storage
{
aligned_storage_t<sizeof(T)> buf;
storage(storage&&) = delete;
public:
storage()
{
new (&buf) T{};
}
T &operator*()
{
return *static_cast<T*>(&buf);
}
void destroy()
{
(**this).~T();
}
};
template<typename T, bool destructor>
struct conditional_storage_destructor
{
storage<T> x;
};
template<typename T>
struct conditional_storage_destructor<T, true> : protected storage<T>
{
storage<T> x;
~conditional_storage_destructor()
{
x.destroy();
}
};
template<typename T>
class wrapper
{
conditional_storage_destructor<T, not is_trivially_destructible<T>::value> x;
public:
T &operator*()
{
return *(x.x);
}
};
int main()
{
static_assert(is_trivially_destructible< wrapper<int> >::value);
static_assert(not is_trivially_destructible< wrapper<vector<int>> >::value);
cout << "executed" << endl;
}
值得庆幸的是,通过 C++20 个约束实现这一点,这几乎会导致原始问题的伪代码既易于理解又易于实现:
#include <type_traits>
#include <optional>
#include <string>
#include <vector>
template< typename ...types >
struct X
{
~X() = default;
~X() requires (!(std::is_trivially_destructible_v<types> && ...))
{
}
};
int main()
{
static_assert(std::is_trivially_destructible_v<
X<>
>);
static_assert(std::is_trivially_destructible_v<
X<float, int, char>
>);
static_assert(!std::is_trivially_destructible_v<
X<std::vector<int>, std::vector<char>>
>);
static_assert(!std::is_trivially_destructible_v<
X<std::string, int, float>
>);
static_assert(std::is_trivially_destructible_v<
X<std::optional<int>, int, float>
>);
}
(神霹雳链接在这里)
使用重载分辨率(C++20 标准 §11.4.7.4 [class.dtor])选择适当的析构函数:
在类的定义结束时,将在该类中声明的预期析构函数之间执行重载解析,并使用空参数列表来选择类的析构函数,也称为所选析构函数。如果过载解析失败,则程序格式不正确。析构函数选择不构成对所选析构函数的引用或使用 ([basic.def.odr]),特别是,可以删除所选析构函数 ([dcl.fct.def.delete])。
整个C++的重载分辨率相当长且复杂,但简而言之,重载分辨率选择满足约束且约束最严格的析构函数(C++20 标准 §12.2.3.1 [over.match.viable]):
从为给定上下文([over.match.funcs])构造的候选函数集中,选择一组可行的函数,通过比较参数转换序列和相关约束([temp.constr.decl])从中选择最佳函数,以获得最佳拟合([over.match.best])。可行函数的选择会考虑相关的约束(如果有)以及参数和函数参数之间的关系,而不是转换序列的排名。
请注意,此策略也可以应用于其他特殊成员函数(构造函数、赋值运算符等)。尽管P0848R3 - 有条件的特殊成员函数提案在最近的 clang 16 版本中仅部分实现,而 gcc>= 10 和 MSVC>= VS 2019 16.8 完全符合。