C++ “制作自定义类型”;“有能力”;(与std::tie兼容)

C++ “制作自定义类型”;“有能力”;(与std::tie兼容),c++,c++11,tuples,std,C++,C++11,Tuples,Std,假设我有一个自定义类型(可以扩展): 如何使此对象的实例可分配给引用的std::tie,即std::tuple Foo foo = ...; int a; string b; std::tie(a, b) = foo; 尝试失败: 无法重载tuple=Foo的赋值运算符,因为赋值运算符是必须是左侧对象成员的二进制运算符之一 所以我试图通过实现一个合适的元组转换操作符来解决这个问题。以下版本失败: 运算符tuple()const 运算符tuple()const 它们在赋值时导致错误,说明

假设我有一个自定义类型(可以扩展):

如何使此对象的实例可分配给引用的
std::tie
,即
std::tuple

Foo foo = ...;

int a;
string b;

std::tie(a, b) = foo;
尝试失败:

无法重载tuple=Foo的赋值运算符,因为赋值运算符是必须是左侧对象成员的二进制运算符之一

所以我试图通过实现一个合适的元组转换操作符来解决这个问题。以下版本失败:

  • 运算符tuple()const
  • 运算符tuple()const
它们在赋值时导致错误,说明“
operator=
没有重载
tuple=Foo
”。我猜这是因为“转换为任何类型X+推导operator=”的模板参数X不一起工作,一次只能转换其中一个

不完美的尝试:

因此,我尝试为领带的确切类型实现一个转换运算符:

  • 运算符tuple()const
  • 运算符元组()
由于类型现在(转换后)完全相同,分配现在可以工作,但这不适用于我希望支持的三种方案:

  • 如果tie绑定了不同但可转换类型的变量(即在客户端将
    int a;
    更改为
    long-long a;
    ),它将失败,因为类型必须完全匹配。这与通常将元组分配给允许可转换类型的引用元组的用法相矛盾
  • 转换运算符需要返回一个必须给定左值引用的tie。这对临时值或常量成员无效。(2)
  • 如果转换运算符不是常量,则对于右侧的
    const Foo
    ,赋值也会失败。要实现const版本的转换,我们需要去掉const主题成员的const。这是丑陋的,可能被滥用,导致未定义的行为
  • 我只看到另一种选择,即提供我自己的
    tie
    function+类和我的“可绑定”对象,这使我不得不复制
    std::tie
    的功能,我不喜欢它(并不是说我觉得很难做到,而是觉得必须这样做是不对的)

    我认为,最终的结论是,这是纯库元组实现的一个缺点。它们不像我们希望的那么神奇

    编辑: 事实证明,似乎没有解决上述所有问题的真正解决方案。一个很好的答案可以解释为什么这是无法解决的。特别是,我想有人解释一下为什么“失败的尝试”不可能奏效


    (1) :一个可怕的技巧是将转换写入模板,并在转换运算符中转换为请求的成员类型。这是一个可怕的黑客行为,因为我不知道在哪里存储这些已转换的成员。在本文中,我使用静态变量,但这不是线程可重入的。

    (2) :可以应用与(1)中相同的hack。
    只有两种方法可以尝试:

  • 使用模板化的赋值运算符:
    您需要从模板化赋值运算符完全匹配的类型公开派生
  • 使用非模板赋值运算符:
    提供对非模板复制运算符所需类型的非显式转换,以便使用
  • 没有第三种选择
  • 在这两种情况下,您的类型都必须包含要分配的元素,不能绕过它

    #include <iostream>
    #include <tuple>
    using namespace std;
    
    struct X : tuple<int,int> {
    };
    
    struct Y {
        int i;
        operator tuple<int&,int&>() {return tuple<int&,int&>{i,i};}
    };
    
    int main()
    {
        int a, b;
        tie(a, b) = make_tuple(9,9);
        tie(a, b) = X{};
        tie(a, b) = Y{};
        cout << a << ' ' << b << '\n';
    }
    
    #包括
    #包括
    使用名称空间std;
    结构X:元组{
    };
    结构{
    int i;
    运算符tuple(){返回tuple{i,i};}
    };
    int main()
    {
    INTA,b;
    tie(a,b)=make_-tuple(9,9);
    tie(a,b)=X{};
    tie(a,b)=Y{};
    不知道为什么当前的尝试失败了
    
    std::tie(a,b)
    生成一个
    std::tuple
    。 此类型与
    std::tuple
    等无关

    std::tuple
    s有几个赋值运算符:

    • 默认赋值运算符,采用
      std::tuple
    • 具有类型参数pack
      U..
      的元组转换赋值运算符模板,该模板采用
      std::tuple
    • 具有两个类型参数的成对转换赋值运算符模板
      U1,U2
      ,它采用
      std::pair
    对于这三个版本,存在复制和移动变体;将
    常量&
    和&
    添加到它们所采用的类型中

    赋值运算符模板必须从函数参数类型(即赋值表达式的RHS类型)推断其模板参数

    如果在
    Foo
    中没有转换运算符,则这些赋值运算符都不能用于
    std::tie(a,b)=Foo
    。 如果将转换运算符添加到
    Foo
    , 然后,只有默认赋值运算符才可行: 模板类型推断不考虑用户定义的转换。 也就是说,不能从类型
    Foo
    推断赋值运算符模板的模板参数

    由于隐式转换序列中只允许一个用户定义的转换,因此转换运算符转换为的类型必须与默认赋值运算符的类型完全匹配。也就是说,它必须使用与
    std::tie
    结果完全相同的元组元素类型

    为了支持元素类型的转换(例如,将
    Foo::a
    分配给
    long
    ),Foo
    的转换运算符必须是一个模板:

    struct Foo {
        int a;
        string b;
        template<typename T, typename U>
        operator std::tuple<T, U>();
    };
    
    structfoo{
    INTA;
    b串;
    模板
    运算符std::tuple();
    };
    
    但是,<代码的元素类型
    struct Foo {
        int a;
        string b;
        template<typename T, typename U>
        operator std::tuple<T, U>();
    };
    
    struct Foo : tuple<const int&, const string&> {
        int a;
        string b;
    
        Foo(int a, string b) :
            tuple{std::tie(this->a, this->b)},
            a{a}, b{b}
        {}
    };
    
    int main()
    {
        Foo foo(0, "bar");
        foo.a = 42;
    
        long long a;
        string b;
    
        tie(a, b) = foo;
        cout << a << ' ' << b << '\n';
    }
    
    42 bar
    
    #include <tuple>
    #include <string>
    #include <iostream>
    #include <functional>
    using namespace std;
    
    
    struct Foo {
        int a;
        string b;
    
        template <template<typename ...Args> class tuple, typename ...Args>
        operator tuple<Args...>() const {
            return forward_as_tuple(get<Args>()...);
        }
    
        template <template<typename ...Args> class tuple, typename ...Args>
        operator tuple<Args...>() {
            return forward_as_tuple(get<Args>()...);
        }
    
        private:
        // This is hacky, may be there is a way to avoid it...
        template <typename T>
        T get()
        { static typename remove_reference<T>::type i; return i; }
    
        template <typename T>
        T get() const
        { static typename remove_reference<T>::type i; return i; }
    
    };
    
    template <>
    int&
    Foo::get()
    { return a; }
    
    template <>
    string&
    Foo::get()
    { return b; }
    
    template <>
    int&
    Foo::get() const
    { return *const_cast<int*>(&a); }
    
    template <>
    string&
    Foo::get() const
    { return *const_cast<string*>(&b); }
    
    int main() {
        Foo foo { 42, "bar" };
        const Foo foo2 { 43, "gah" };
    
        int a;
        string b;
    
        tie(a, b) = foo;
        cout << a << ", " << b << endl;
    
        tie(a, b) = foo2;
        cout << a << ", " << b << endl;
    
    }