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

ee7vknir  于 2023-08-09  发布在  其他
关注(0)|答案(3)|浏览(63)

我写了这样一个静态类型转换的模板函数作为练习:

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

pgpifvop

pgpifvop1#

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

x759pob2

x759pob22#

您的模板根本不能替代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


我的建议是继续使用static_cast。这也会帮助其他人阅读你的代码;他们几乎肯定熟悉static_cast,但他们可能需要查找sCast,至少在第一次遇到该函数时是这样。另外,IDE将突出显示static_cast,与函数模板调用不同。
总的来说,我不认为不惜一切代价缩短代码是一个好主意。如果通过多输入几个字符来增加可读性,那么这是值得的。如果你不想为打字而烦恼,几乎每个文本编辑器都有搜索和替换功能。

vfh0ocws

vfh0ocws3#

在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));
}


您的函数模板不能正确接受纯右值。上面的“完美转发”将纯右值更改为x值。举例来说:

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

相关问题