英文:
Is storing a reference to a (possibly) temporary object legal as long as the reference doesn't outlive the object?
问题
在阅读了std::variant
和方便的overloaded
技巧后,我给自己设定了一个挑战(练习),写一个帮助函数,允许我这样写:
visit(variant).with([](int){}, [](char){});
而不是:
std::visit(overloaded{[](int){}, [](char){}}, variant);
我还希望它在其他情况下也能工作,比如:
VariantT variant;
VisitorT visitor;
visit(variant).with(visitor);
visit(VariantT{std::in_place_type<int>, 1}).with(visitor);
const VariantT constVariant;
visit(constVariant).with([](auto&&){});
你明白我的意思。
以下是我想出的代码(请注意,我已将名称从overloaded
更改为Visitor
):
#include <iostream>
#include <string>
#include <utility>
#include <variant>
template <typename... T>
struct Visitor : public T...
{
using T::operator()...;
};
template <typename... T>
Visitor(T...)->Visitor<T...>;
template <typename VariantT>
struct Helper
{
Helper(VariantT& variant)
: m_variant{variant}
{}
Helper(const Helper&) = delete;
Helper(Helper&&) = delete;
Helper& operator=(const Helper&) = delete;
Helper& operator=(Helper&&) = delete;
~Helper() = default;
template <typename VisitorT, typename... VisitorsT>
decltype(auto) with(VisitorT&& visitor,
VisitorsT&&... visitors) &&
{
if constexpr (sizeof...(visitors) == 0)
{
return std::visit(std::forward<VisitorT>(visitor), m_variant);
}
else
{
return std::visit(Visitor{visitor, visitors...}, m_variant);
}
}
private:
VariantT& m_variant;
};
template <typename VariantT>
decltype(auto) visit(VariantT&& variant)
{
// no forwarding here, even if an rvalue was passed, pass an lvalue ref
return Helper{variant};
}
int main()
{
visit(std::variant<int>{std::in_place_type<int>, -7})
.with([](int i) { std::cout << "got an int: " << i << std::endl; },
[](std::string str) { std::cout << "got a string: " << str << std::endl; });
std::variant<int, std::string> v = "String";
visit(v).with([](int i) { std::cout << "got an int: " << i << std::endl; },
[](std::string str) { std::cout << "got a string: " << str << std::endl; });
visit(v).with([](int& i) { i += 7; },
[](std::string& str) { str += "modified"; });
std::cout << visit(v).with([](int i) { return std::to_string(i); },
[](std::string str) { return str; }) << std::endl;
}
问题是:像这样在Helper
中存储引用是否完全合法?我的理解是,临时对象在表达式结束时存活,所以这应该没问题?它会做正确的事情吗?代码中是否有任何潜在问题?
我已经在MSVC和GCC上测试了这段代码,没有发现任何问题,但这并不意味着它在所有情况下都可以正常工作。
英文:
After reading about std::variant
and the handy overloaded
trick I set myself a challenge (an exercise) to write a helper that would allow me to write
visit(variant).with([](int){}, [](char){});
instead of
std::visit(overloaded{[](int){}, [](char){}}, variant);
I'd also like it to work in other cases, like
VariantT variant;
VisitorT visitor;
visit(variant).with(visitor);
visit(VariantT{std::in_place_type<int>, 1}).with(visitor);
const VariantT constVariant;
visit(constVariant).with([](auto&&){});
You get the idea.
Here's what I came up with (note that I've changed the name from overloaded
to Visitor
:
#include <iostream>
#include <string>
#include <utility>
#include <variant>
template <typename... T>
struct Visitor : public T...
{
using T::operator()...;
};
template <typename... T>
Visitor(T...)->Visitor<T...>;
template <typename VariantT>
struct Helper
{
Helper(VariantT& variant)
: m_variant{variant}
{}
Helper(const Helper&) = delete;
Helper(Helper&&) = delete;
Helper& operator=(const Helper&) = delete;
Helper& operator=(Helper&&) = delete;
~Helper() = default;
template <typename VisitorT, typename... VisitorsT>
decltype(auto) with(VisitorT&& visitor,
VisitorsT&&... visitors) && // this function is ref-qualified so we can only call this on a temp object and we can
// be sure that the variant lives at least as long as us
{
if constexpr (sizeof...(visitors) == 0)
{
return std::visit(std::forward<VisitorT>(visitor), m_variant);
}
else
{
return std::visit(Visitor{visitor, visitors...}, m_variant);
}
}
private:
VariantT& m_variant;
};
template <typename VariantT>
decltype(auto) visit(VariantT&& variant)
{
// no forwarding here, even if an rvalue was passed, pass an lvalue ref
return Helper{variant};
}
int main()
{
visit(std::variant<int>{std::in_place_type<int>, -7})
.with([](int i) { std::cout << "got an int: " << i << std::endl; },
[](std::string str) { std::cout << "got a string: " << str << std::endl; });
std::variant<int, std::string> v = "String";
visit(v).with([](int i) { std::cout << "got an int: " << i << std::endl; },
[](std::string str) { std::cout << "got a string: " << str << std::endl; });
visit(v).with([](int& i) { i += 7; },
[](std::string& str) { str += "modified"; });
std::cout << visit(v).with([](int i) { return std::to_string(i); },
[](std::string str) { return str; }) << std::endl;
}
The question is: is storing references in the Helper
like that perfectly legal?
My understanding is that temporary objects live until the end of the expression, so I guess this is ok?
Will it do The Right Thing? Are there any pitfalls in that code?
I've tested this code on both msvc and gcc and I'm not seeing anything wrong, but it doesn't mean this works fine in all cases.
答案1
得分: 1
唯一明显的问题是您可以有以下内容:
decltype(auto) silly() {
std::variant<int, std::string> v = "String";
return visit(v);
}
int main()
{
silly().with([](auto){ std::cout << "dangling" << std::endl; };
}
但在审查中很容易发现这种问题。
英文:
The only obvious problem is that you can have things like
decltype(auto) silly() {
std::variant<int, std::string> v = "String";
return visit(v);
}
int main()
{
silly().with([](auto){ std::cout << "dangling" << std::endl; };
}
But such things are relatively easy to spot in review.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论