提问人:spdie 提问时间:8/1/2023 最后编辑:HolyBlackCatspdie 更新时间:8/1/2023 访问量:89
C++14 中的自定义静态转换函数模板
Custom static cast function template in C++14
问:
我已经为静态转换编写了这样一个模板函数作为练习:
template<typename cT, typename T>
inline constexpr cT sCast(T carg) {
return static_cast<cT>(carg);
}
我只是不想一直打字,所以我挑战自己用一个更短的名字来编写自己的模板函数。但是,我知道我可以在一行代码中做一些更容易的事情:static_cast<someT>(some_obj)
#define sCast static_cast
...
sCast<someT>(some_obj);
但正如我所说,我想设计一个功能模板,只是为了锻炼。
现在我的问题是:这个函数模板是否尽可能高效,如果没有,它怎么能更好?模板函数是否满足RVO优化的条件?或者也许我只是一个完美主义者,这样的想法是浪费时间?
答:
此模板不如仅使用 .原因是该函数必须显式推送到堆栈上,并且其参数必须在事后清理。大多数编译器会内联它(==>就好像你写的而不是上面的函数),但如果你的编译器选择不内联它,它会影响你的性能(即使只是轻微的,而且不太可能有人会注意到这一点)static_cast
static_cast
您的模板根本不是替代品。static_cast
考虑涉及引用的情况。如果不显式指定第二个参数,最终将无法推断出引用:
struct Test
{
Test()
{
std::cout << "Test::Test()\n";
}
Test(Test&&)
{
std::cout << "Test::Test(Test&&)\n";
}
Test(Test const&)
{
std::cout << "Test::Test(Test const&)\n";
}
Test& operator=(Test&&)
{
std::cout << "Test::operator=(Test&&)\n";
return *this;
}
Test& operator=(Test const&)
{
std::cout << "Test::operator=(Test const&)\n";
return *this;
}
~Test()
{
std::cout << "Test::~Test()\n";
}
};
template<typename cT, typename T>
inline constexpr cT sCast(T carg) {
return static_cast<cT>(carg);
}
int main()
{
Test original;
std::cout << "before static_cast\n";
Test const& withStaticCast = static_cast<Test const&>(original);
std::cout << "before sCast\n";
Test const& withScast = sCast<Test const&>(original);
std::cout << "after sCast\n";
std::cout << std::boolalpha
<< (&original == &withStaticCast) << '\n'
<< (&original == &withScast) << '\n';
}
输出:
Test::Test()
before static_cast
before sCast
Test::Test(Test const&)
Test::~Test()
after sCast
true
false
Test::~Test()
正如你所看到的,有一个额外的副本正在制作,这个副本甚至在下一个表达式之前被销毁,这很容易导致未定义的行为。sCast
使用转发引用可以解决此问题,但在某些情况下,您最终仍会得到不同的语义:
template<typename cT, typename T>
constexpr cT sCast(T&& carg)
{
return static_cast<cT>(std::forward<T>(carg));
}
具有不同语义的代码:
std::cout << "before static_cast\n";
Test withStaticCast = static_cast<Test>(Test());
std::cout << "before sCast\n";
Test withScast = sCast<Test>(Test{});
std::cout << "after sCast\n";
输出
before static_cast
Test::Test()
before sCast
Test::Test()
Test::Test(Test&&)
Test::~Test()
after sCast
我的建议是继续使用 .这也将帮助其他人阅读您的代码;他们几乎肯定很熟悉,但他们很可能需要抬头看一下,至少在他们第一次遇到这个功能时是这样。此外,IDE 的突出显示方式与函数模板调用不同。static_cast
static_cast
sCast
static_cast
总的来说,我认为不惜一切代价缩短代码不是一个好主意。如果通过多输入几个字符来增加可读性,那么值得付出努力。如果您不能为打字而烦恼,那么几乎每个文本编辑器中仍然提供搜索和替换功能......
在 C++14 中:转换为非引用类型将创建一个副本。该副本受 RVO 的约束,但static_cast永远不会有额外的临时副本/移动。例如:
struct X { operator std::string(); operator std::mutex(); };
// Same as `X{}.operator std::string();`
static_cast<std::string>(X{});
// Same as `std::string(X{}.operator std::string())`: May move, may be elided
sCast<std::string>(X{});
static_cast<std::mutex>(X{}); // Same as `X{}.operator std::mutex();`
//sCast<std::mutex>(X{}); // Does not compile: mutex not movable
C++17 中不存在此问题,因为必须省略该移动。
还有特殊的措辞。生存期扩展可以通过静态强制转换来完成,如果强制转换的类型不依赖于类型,则它们不依赖于类型。static_cast
const X& x = static_cast<const X&>(X{}); // This will extend the lifetime of the temporary
const X& y = sCast<const X&>(X{}); // This will be a dangling reference
struct TempFn { template<typename> void f(); };
template<typename T>
void g(T t) {
// OK: static_cast<TempFn>(t) is not type-dependent
static_cast<TempFn>(t).f<int>();
// Error: sCast<TempFn>(t) is type-dependent
//sCast<TempFn>(t).f<int>();
sCast<TempFn>(t).template f<int>();
}
这不能使用函数模板进行模拟。
您的函数也无法正确处理右值,并且总是复制其参数。例如:
struct Y : X { operator int() &&; };
static_cast<int>(Y{}); // Works
sCast<int>(Y{}); // Tries to cast lvalue `carg` to int. Does not work.
Y y;
static_cast<X&>(y); // Works: reference to base of y
sCast<X&>(y); // Dangling reference to base of temporary
这可以通过完美转发来解决:
template<typename cT, typename T>
inline constexpr cT sCast(T&& carg) {
return static_cast<cT>(std::forward<T>(carg));
}
您的函数对 SFINAE 不友好。在重载解析期间,它将声明始终可调用。例如:
template<typename Integral>
std::enable_if_t<std::is_integral_v<Integral>> f(Integral x);
template<typename Stringy>
decltype(void(sCast<std::string>(std::declval<Stringy>()))) f(Stringy&& s) {
std::string str = sCast<std::string>(std::forward<Stringy>(s));
return f(parseint(str));
}
// sCast<std::string>(std::declval<Stringy>()) isn't a substitution failure
// even if Stringy is `int`
您可以通过在将失败时将其设置为替换失败来解决此问题。最简单的是这样的:static_cast
template<typename cT, typename T>
inline constexpr decltype(static_cast<cT>(std::declval<T>())) sCast(T&& carg) {
return static_cast<cT>(std::forward<T>(carg));
}
函数模板无法正确获取 pr值。上面的“完美转发”将 prvalues 更改为 xvalues。例如:
// The same as `X{}`.
static_cast<X>(X{});
// Cannot be elided. Required to move from a temporary, and for `X` to be move constructible.
// Possibly moves from two temporaries as described in the first point in C++14
sCast<X>(X{});
// C++23
// operator int() can only be called on a prvalue
struct Z { Z() = default; Z(Z&&) = delete; operator int(this Z); };
static_cast<int>(Z{}); // Works
sCast<int>(Z{}); // Tries to call operator int on an xvalue. Cannot move into object parameter.
评论
static_cast
static_cast