【问题标题】:Construct boost::type_erasure::any from C string but store as std::string从 C 字符串构造 boost::type_erasure::any 但存储为 std::string
【发布时间】:2017-08-07 20:27:17
【问题描述】:

是否可以通过从字符串文字或char const* 构造和分配自动将字符串复制到std::string 并将其存储在boost::type_erasure::any 对象中的方式声明boost::type_erasure::any

默认情况下,boost::type_erasure::any 只存储字符串指针。

目的是避免在我的any 类型的用户为其分配一个字符串指针时出现错误,假设将复制(如std::string 所做的那样),然后字符串的生命周期在我之前结束any 被读取,导致崩溃。

例子:

#include <boost/type_erasure/operators.hpp>
#include <boost/type_erasure/any.hpp>
#include <boost/type_erasure/any_cast.hpp>
#include <boost/type_erasure/relaxed.hpp>
#include <boost/mpl/vector.hpp>

#include <iostream>

namespace te = boost::type_erasure;

using my_any = te::any< boost::mpl::vector<
    te::copy_constructible<>,
    te::destructible<>,
    te::typeid_<>,
    te::relaxed
    /* I believe some changes here would do the trick */
    >>;

using namespace std;

int main()
{
    // Store an std::string by explicitly calling string constructor.
    my_any a = string("abc");

    // The following should copy-construct an std::string too but it just stores
    // the string pointer.
    my_any b = "abc";

    // Works as expected.
    cout << te::any_cast<string>( a ) << endl;

    // This crashes because the underlying type of b is not std::string.
    // With some changes to the my_any type this shouldn't crash anymore.
    cout << te::any_cast<string>( b ) << endl;
}

Live Demo.

【问题讨论】:

    标签: c++ string c++11 boost boost-type-erasure


    【解决方案1】:

    不,any 存储任何东西。 const char* 什么都可以。

    注意"hello"sstd::string 类型的文字。

    【讨论】:

    • 您应该提到"hello"s 仅适用于 C++14 及更高版本。
    • 我希望在没有“s”后缀的情况下这样做,因为它只是调用std::string 构造函数的语法糖。目的是避免my_any 的用户为其分配字符串指针时出现错误源,假设将进行复制(如std::string 所做的那样),然后指针的生命周期在读取my_any 之前结束,导致崩溃。
    • @zett42 在没有首先检查或证明它是那个类型的情况下,你永远不应该从 any 中得到一个类型。如果不让取出它的人证明它是什么,你永远不应该在 any 中输入一个类型。如果您想要一组枚举类型,请考虑变体。如果您想支持放入 any 的任何内容必须可转换为字符串(甚至必须可选地转换为运行时检查失败的字符串),您可能会这样做。
    • @Yakk 首先检查它或证明它是那种类型 ...我肯定会这样做。同时,我不想排除用户提供字符串文字。我可以将any 隐藏在采用静态类型的模板化“setter”后面,并为char const* 提供专门化,在存储到any 之前转换为std::string。虽然我认为any 可以在提供正确概念的情况下自动进行这种转换(类似于this example)。
    • 我已经发布了an answer,希望能阐明any 的预期用途。
    【解决方案2】:

    我正在发布我自己问题的答案,希望澄清boost::type_erasure::any 的预期用途,而不会使原始问题过于冗长。

    这个答案显示了一种可能的解决方法,将boost::type_erasure::any 隐藏在另一个类的接口后面,并为所有可转换为std::string 的类型提供setter 方法的重载。此重载负责将 char 指针和 char 数组转换为 std::string

    我认为一个额外的重载并没有那么糟糕,但理想情况下我想避免那个样板文件并使any 类型知道如何转换 C 字符串。这让我们回到了我的original question

    #include <iostream>
    #include <unordered_map>
    #include <string>
    #include <type_traits>
    
    #include <boost/type_erasure/operators.hpp>
    #include <boost/type_erasure/any.hpp>
    #include <boost/type_erasure/any_cast.hpp>
    #include <boost/type_erasure/relaxed.hpp>
    #include <boost/mpl/vector.hpp>
    
    namespace te = boost::type_erasure;
    
    // A class to store attributes of any type by name.
    class Attributes
    {
    public:
        using Key = std::string;
    
        // A type that can store any value (similar to std::any).
        using AnyValue = te::any< boost::mpl::vector<
            te::copy_constructible<>,
            te::destructible<>,
            te::typeid_<>,
            te::relaxed
            >>;
    
        // Overload for all types that ain't strings.
        template< typename T >
            std::enable_if_t< !std::is_convertible<T, std::string>::value, 
        void > SetAttr( Key const& name, T&& value )
        {
            m_attr.insert_or_assign( name, std::forward<T>( value ) );
        }
    
        // Convert to std::string for all convertible types
        // (char pointer and char array included).
        template< typename T >
            std::enable_if_t< std::is_convertible<T, std::string>::value, 
        void > SetAttr( Key const& name, T&& value )
        {
            m_attr.insert_or_assign( name, std::string( std::forward<T>( value ) ) );
        }
    
        template< typename T >
        T GetAttr( Key const& name ) const 
        { 
            return te::any_cast<T>( m_attr.at( name ) ); 
        }
    
    private:
        std::unordered_map<Key, AnyValue> m_attr;
    };
    

    以下示例显示了如何将不同类型的字符串传递给Attributes::SetAttr() 并通过Attributes::GetAttr&lt;std::string&gt;() 进行一般查询:

    using namespace std;
    
    Attributes a;
    // Works even w/o special care.
    a.SetAttr( "key1", string("foo") );
    
    // Without the SetAttr() overload for strings, user would have to remind 
    // to cast back to char const* when calling MyClass::GetAttr().
    a.SetAttr( "key2", "bar" );
    
    // Without the SetAttr() overload for strings, a later call to GetAttr()
    // would cause a crash because we are passing pointers to temporary objects.
    {
        // test arrays
        char temp1[] = { 'b', 'a', 'z', 0 };
        a.SetAttr( "key3", temp1 );
        char const temp2[] = { 'b', 'i', 'm', 0 };
        a.SetAttr( "key4", temp2 );
    
        // test pointers
        a.SetAttr( "key5", &temp1[0] );
        a.SetAttr( "key6", &temp2[0] );
    }
    
    try
    {
        // When getting a string attribute we no longer have to care about how it was
        // passed to SetAttr(), we can simply cast to std::string in all cases.
        cout << "'" << a.GetAttr<string>( "key1" ) << "'" << endl;
        cout << "'" << a.GetAttr<string>( "key2" ) << "'" << endl;
        cout << "'" << a.GetAttr<string>( "key3" ) << "'" << endl;
        cout << "'" << a.GetAttr<string>( "key4" ) << "'" << endl;
        cout << "'" << a.GetAttr<string>( "key5" ) << "'" << endl;
        cout << "'" << a.GetAttr<string>( "key6" ) << "'" << endl;
    }
    // boost::type_erasure::bad_any_cast or std::out_of_range
    catch( std::exception& e )
    {
        cout << "Error: " << e.what() << endl;
    }
    

    Live Demo.

    【讨论】:

    • SetAttr 应该采用 T&amp;&amp; 而不是 T const&amp; 允许搬入(假设您的任何版本都支持它)
    • @Yakk 谢谢,我已经根据你的建议更新了代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-06-12
    • 2011-06-06
    • 1970-01-01
    • 1970-01-01
    • 2011-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多