C++14 中的自定义静态转换函数模板

Custom static cast function template in C++14

提问人:spdie 提问时间:8/1/2023 最后编辑:HolyBlackCatspdie 更新时间:8/1/2023 访问量:89

问:

我已经为静态转换编写了这样一个模板函数作为练习:

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优化的条件?或者也许我只是一个完美主义者,这样的想法是浪费时间?

C++ 模板 转换 C++14 返回值优化

评论

4赞 Some programmer dude 8/1/2023
函数,尤其是宏,往往会使代码更难阅读、理解和维护。这可能是一个很好的练习,但请不要过度使用它。
0赞 BoP 8/1/2023
关键字是故意选择丑陋的,因此它在代码中脱颖而出,因此您在使用它之前应该三思而后行。将其隐藏在宏中绝对不是解决丑陋的方法。不怎么使用它是。static_cast
0赞 HolyBlackCat 8/1/2023
C型演员怎么样?如果您不强制转换为指针/引用,则它们在 上是安全的,并且语法在理论上尽可能短。static_cast
0赞 Pepijn Kramer 8/1/2023
不想输入非常标准的东西是错误的方法。只需键入标准的 C++,任何人都可以理解您的代码。无论如何,无论如何,您都应该小心static_cast。您能向我们解释一下您在哪里使用,想要使用static_cast以及为什么?
1赞 HolyBlackCat 8/1/2023
@PepijnKramer不,我是认真的。我什至认为无条件禁止它们(当目标类型不是指针/引用时)是货物崇拜的一种形式。

答:

1赞 HelpfulHelper 8/1/2023 #1

此模板不如仅使用 .原因是该函数必须显式推送到堆栈上,并且其参数必须在事后清理。大多数编译器会内联它(==>就好像你写的而不是上面的函数),但如果你的编译器选择不内联它,它会影响你的性能(即使只是轻微的,而且不太可能有人会注意到这一点)static_caststatic_cast

0赞 fabian 8/1/2023 #2

您的模板根本不是替代品。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_caststatic_castsCaststatic_cast

总的来说,我认为不惜一切代价缩短代码不是一个好主意。如果通过多输入几个字符来增加可读性,那么值得付出努力。如果您不能为打字而烦恼,那么几乎每个文本编辑器中仍然提供搜索和替换功能......

0赞 Artyer 8/1/2023 #3

在 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.