Is storing a reference to a (possibly) temporary object legal as long as the reference doesn't outlive the object?

huangapple go评论97阅读模式
英文:

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&lt;int&gt;, 1}).with(visitor);
const VariantT constVariant;
visit(constVariant).with([](auto&amp;&amp;){});

You get the idea.

Here's what I came up with (note that I've changed the name from overloaded to Visitor:

#include &lt;iostream&gt;
#include &lt;string&gt;
#include &lt;utility&gt;
#include &lt;variant&gt;
template &lt;typename... T&gt;
struct Visitor : public T...
{
using T::operator()...;
};
template &lt;typename... T&gt;
Visitor(T...)-&gt;Visitor&lt;T...&gt;;
template &lt;typename VariantT&gt;
struct Helper
{
Helper(VariantT&amp; variant)
: m_variant{variant}
{}
Helper(const Helper&amp;) = delete;
Helper(Helper&amp;&amp;) = delete;
Helper&amp; operator=(const Helper&amp;) = delete;
Helper&amp; operator=(Helper&amp;&amp;) = delete;
~Helper() = default;
template &lt;typename VisitorT, typename... VisitorsT&gt;
decltype(auto) with(VisitorT&amp;&amp; visitor,
VisitorsT&amp;&amp;... visitors) &amp;&amp; // 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&lt;VisitorT&gt;(visitor), m_variant);
}
else
{
return std::visit(Visitor{visitor, visitors...}, m_variant);
}
}
private:
VariantT&amp; m_variant;
};
template &lt;typename VariantT&gt;
decltype(auto) visit(VariantT&amp;&amp; variant)
{
// no forwarding here, even if an rvalue was passed, pass an lvalue ref
return Helper{variant};
}
int main()
{
visit(std::variant&lt;int&gt;{std::in_place_type&lt;int&gt;, -7})
.with([](int i) { std::cout &lt;&lt; &quot;got an int: &quot; &lt;&lt; i &lt;&lt; std::endl; },
[](std::string str) { std::cout &lt;&lt; &quot;got a string: &quot; &lt;&lt; str &lt;&lt; std::endl; });
std::variant&lt;int, std::string&gt; v = &quot;String&quot;;
visit(v).with([](int i) { std::cout &lt;&lt; &quot;got an int: &quot; &lt;&lt; i &lt;&lt; std::endl; },
[](std::string str) { std::cout &lt;&lt; &quot;got a string: &quot; &lt;&lt; str &lt;&lt; std::endl; });
visit(v).with([](int&amp; i) { i += 7; },
[](std::string&amp; str) { str += &quot;modified&quot;; });
std::cout &lt;&lt; visit(v).with([](int i) { return std::to_string(i); },
[](std::string str) { return str; }) &lt;&lt; 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&lt;int, std::string&gt; v = &quot;String&quot;;
return visit(v);
}
int main()
{
silly().with([](auto){ std::cout &lt;&lt; &quot;dangling&quot; &lt;&lt; std::endl; };
}

But such things are relatively easy to spot in review.

huangapple
  • 本文由 发表于 2020年1月3日 20:37:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/59578775.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定