将 [] 运算符重载到 return 变体类型
overload [] operator to return a variant type
编辑:多亏了答案,我才能够用我的代码解决所有问题。我 post 这里是解决方案:它可能对将来的某些人有用。特别是,使用代理 class 的建议非常有用!该示例并未考虑所有情况,但向变体添加另一种类型应该是微不足道的!
我正在编写一个 C++ (C11 - Linux) 自定义 class,其行为类似于无序映射 {键,值}。我想重载 [] 运算符,以便我可以使用与无序映射相同语法的 class:object[key] 会 return 值 。
问题是我需要 object[key] 到 return 一个变体类型。我可以在内部将 value 存储为字符串或结构,但是当我使用 object[key]、 检索它时,我需要returned 值是 int、float 或 string 取决于一些内部在 运行 时间 确定的条件。
这就是我考虑使用 boost::variant 库 的原因……但我愿意接受任何其他建议。唯一的限制是 test class (在示例中)必须编译为共享库 .so 并且代码必须与 C11 兼容(我的意思是可由 GNU 编译g++ 4.8.5).
我写了一个简单的例子来展示我想要什么样的行为这个例子没有任何意义。这只是为了说明我遇到的错误类型。我写的真正的 class 有不同的结构,但是 bool::variant 和 operator [] 重载的用法是相同的。
test.cpp
#include <boost/variant.hpp>
typedef boost::variant<int, float> test_t;
class Test
{
int i ;
float f;
void set(int randomint, test_t tmp){
if ( randomint == 0 ) i = boost::get<int>(tmp);
else f = boost::get<float>(tmp);
}
test_t get(int randomint){
if ( randomint == 0 ) return i;
else return f;
}
struct IntOrFloat {
int randomint;
Test *proxy;
explicit operator int () const
{ return boost::get<int>(proxy->get(randomint)); }
void operator= (int tmp)
{ proxy->set(randomint, tmp); }
explicit operator float () const
{ return boost::get<float>(proxy->get(randomint)); }
void operator= (float tmp)
{ proxy->set(randomint, tmp); }
};
public:
IntOrFloat operator [](int randomint)
{ return IntOrFloat{randomint, this}; }
const IntOrFloat operator [](int randomint) const
{ return IntOrFloat{randomint, (Test *) this}; }
};
main.cpp
#include <iostream>
#include <boost/variant.hpp>
#include "test.cpp"
#define INTEGER 0
#define FLOAT 1
int main (void) {
Test test;
int i = 3;
float f = 3.14;
test[INTEGER] = i;
test[FLOAT] = f;
int x = (int) test[INTEGER];
float y = (float) test[FLOAT];
std::cout << x << std::endl;
std::cout << y << std::endl;
return 0;
}
编译和运行
g++ -fPIC -std=c++11 -shared -rdynamic -o test.so test.cpp
g++ -std=c++11 -o test main.cpp -Lpath/to/the/test.so -l:test.so
LD_LIBRARY_PATH="path/to/the/test.so" ./test
当您使用 return i
时,幕后发生的事情是创建一个 test_t
类型的临时对象来封装 int
值。这在函数 test::test_variant
中工作正常,因为 return 类型是 test_t
。这在函数 test::operator[]
中不起作用,因为 return 类型是 test_t&
。该语言禁止创建对临时对象的可修改(左值)引用。
完成此工作的一种方法是将 test_t
类型的数据成员添加到您的 class,您的测试函数 operator[]
设置此成员并 return正在处理它而不是 return 临时处理。您真正的 class 很可能会做一些不同的事情。
在 C++ 中,重载决策不会发生在 return 类型上,因此给定
int foo() { return 0; }
float foo() { return 0.f; }
编译器没有认可的方式来区分
int x = foo();
float f = foo();
。使用转换运算符重载有一个技巧:
#include <iostream>
struct IntOrFloat {
operator int () const {
std::cout << "returning int\n";
return 0;
}
operator float () const {
std::cout << "returning float\n";
return 0.f;
}
};
IntOrFloat foo() { return IntOrFloat(); }
int main () {
int x = foo();
float f = foo();
}
您可以通过转换 explicit
:
来强制更加冗长
explicit operator int () const ...
explicit operator float () const ...
int x = static_cast<int>(foo());
int x = float(foo()); // old-style-cast
此代理(或其他转换运算符技巧)可以模拟 return 类型重载解析。
这个想法是在搜索支持 <euclidian vector> * <euclidian vector>
语法的解决方案时产生的,即 operator*
表示 点积 或 向量乘积,取决于乘积分配给的变量类型。
最后,它不是很实用,也没有对可读性做出积极贡献。由于以下几个原因,更详细的形式 dot(vec, vec)
和 cross(vec, vec)
更优越,其中:
- 最小惊奇原则:计算机图形学界已经习惯了术语 "dot" 和 "cross"
- 不那么神秘的错误消息:因为这种代理技术在 C++ 中不是惯用的,所以人们不习惯这种临时间接产生的错误消息
时间 and/or 空间局部性:您本质上是 return 一个包含代码的闭包,它可以在许多地方执行多次。这可能是双重糟糕的,因为它不能(实际上,确实)与auto &
种声明一起工作得很好:
int main () {
const auto &f = foo();
const int g = f;
const int h = f;
std::cout << (int)f << "\n";
}
这会多次打印某些内容,与最少意外原则齐头并进。当然,如果您的代理基本上只是转发现成可用的值,那么这种情况就不那么严重了。但是错误信息不会变得更好!
请注意,您还可以合并模板转换运算符重载和狂野的元编程。虽然值得进行有趣的实验,但我不想将其放入生产代码库中,因为维护性和可读性甚至会降低。
还剩下什么?无限可能;但一些最可行的:
- 变体数据类型
- 元组数据类型(查看
std::tuple
,它在不同成员类型的情况下带有转换运算符)
- 不同的习语(例如命名方法而不是运算符方法)
- 不同的算法
- 不同的数据结构
- 不同的设计模式
编辑:多亏了答案,我才能够用我的代码解决所有问题。我 post 这里是解决方案:它可能对将来的某些人有用。特别是,使用代理 class 的建议非常有用!该示例并未考虑所有情况,但向变体添加另一种类型应该是微不足道的!
我正在编写一个 C++ (C11 - Linux) 自定义 class,其行为类似于无序映射 {键,值}。我想重载 [] 运算符,以便我可以使用与无序映射相同语法的 class:object[key] 会 return 值 。
问题是我需要 object[key] 到 return 一个变体类型。我可以在内部将 value 存储为字符串或结构,但是当我使用 object[key]、 检索它时,我需要returned 值是 int、float 或 string 取决于一些内部在 运行 时间 确定的条件。
这就是我考虑使用 boost::variant 库 的原因……但我愿意接受任何其他建议。唯一的限制是 test class (在示例中)必须编译为共享库 .so 并且代码必须与 C11 兼容(我的意思是可由 GNU 编译g++ 4.8.5).
我写了一个简单的例子来展示我想要什么样的行为这个例子没有任何意义。这只是为了说明我遇到的错误类型。我写的真正的 class 有不同的结构,但是 bool::variant 和 operator [] 重载的用法是相同的。
test.cpp
#include <boost/variant.hpp>
typedef boost::variant<int, float> test_t;
class Test
{
int i ;
float f;
void set(int randomint, test_t tmp){
if ( randomint == 0 ) i = boost::get<int>(tmp);
else f = boost::get<float>(tmp);
}
test_t get(int randomint){
if ( randomint == 0 ) return i;
else return f;
}
struct IntOrFloat {
int randomint;
Test *proxy;
explicit operator int () const
{ return boost::get<int>(proxy->get(randomint)); }
void operator= (int tmp)
{ proxy->set(randomint, tmp); }
explicit operator float () const
{ return boost::get<float>(proxy->get(randomint)); }
void operator= (float tmp)
{ proxy->set(randomint, tmp); }
};
public:
IntOrFloat operator [](int randomint)
{ return IntOrFloat{randomint, this}; }
const IntOrFloat operator [](int randomint) const
{ return IntOrFloat{randomint, (Test *) this}; }
};
main.cpp
#include <iostream>
#include <boost/variant.hpp>
#include "test.cpp"
#define INTEGER 0
#define FLOAT 1
int main (void) {
Test test;
int i = 3;
float f = 3.14;
test[INTEGER] = i;
test[FLOAT] = f;
int x = (int) test[INTEGER];
float y = (float) test[FLOAT];
std::cout << x << std::endl;
std::cout << y << std::endl;
return 0;
}
编译和运行
g++ -fPIC -std=c++11 -shared -rdynamic -o test.so test.cpp
g++ -std=c++11 -o test main.cpp -Lpath/to/the/test.so -l:test.so
LD_LIBRARY_PATH="path/to/the/test.so" ./test
当您使用 return i
时,幕后发生的事情是创建一个 test_t
类型的临时对象来封装 int
值。这在函数 test::test_variant
中工作正常,因为 return 类型是 test_t
。这在函数 test::operator[]
中不起作用,因为 return 类型是 test_t&
。该语言禁止创建对临时对象的可修改(左值)引用。
完成此工作的一种方法是将 test_t
类型的数据成员添加到您的 class,您的测试函数 operator[]
设置此成员并 return正在处理它而不是 return 临时处理。您真正的 class 很可能会做一些不同的事情。
在 C++ 中,重载决策不会发生在 return 类型上,因此给定
int foo() { return 0; }
float foo() { return 0.f; }
编译器没有认可的方式来区分
int x = foo();
float f = foo();
。使用转换运算符重载有一个技巧:
#include <iostream>
struct IntOrFloat {
operator int () const {
std::cout << "returning int\n";
return 0;
}
operator float () const {
std::cout << "returning float\n";
return 0.f;
}
};
IntOrFloat foo() { return IntOrFloat(); }
int main () {
int x = foo();
float f = foo();
}
您可以通过转换 explicit
:
explicit operator int () const ...
explicit operator float () const ...
int x = static_cast<int>(foo());
int x = float(foo()); // old-style-cast
此代理(或其他转换运算符技巧)可以模拟 return 类型重载解析。
这个想法是在搜索支持 <euclidian vector> * <euclidian vector>
语法的解决方案时产生的,即 operator*
表示 点积 或 向量乘积,取决于乘积分配给的变量类型。
最后,它不是很实用,也没有对可读性做出积极贡献。由于以下几个原因,更详细的形式 dot(vec, vec)
和 cross(vec, vec)
更优越,其中:
- 最小惊奇原则:计算机图形学界已经习惯了术语 "dot" 和 "cross"
- 不那么神秘的错误消息:因为这种代理技术在 C++ 中不是惯用的,所以人们不习惯这种临时间接产生的错误消息
时间 and/or 空间局部性:您本质上是 return 一个包含代码的闭包,它可以在许多地方执行多次。这可能是双重糟糕的,因为它不能(实际上,确实)与
auto &
种声明一起工作得很好:int main () { const auto &f = foo(); const int g = f; const int h = f; std::cout << (int)f << "\n"; }
这会多次打印某些内容,与最少意外原则齐头并进。当然,如果您的代理基本上只是转发现成可用的值,那么这种情况就不那么严重了。但是错误信息不会变得更好!
请注意,您还可以合并模板转换运算符重载和狂野的元编程。虽然值得进行有趣的实验,但我不想将其放入生产代码库中,因为维护性和可读性甚至会降低。
还剩下什么?无限可能;但一些最可行的:
- 变体数据类型
- 元组数据类型(查看
std::tuple
,它在不同成员类型的情况下带有转换运算符) - 不同的习语(例如命名方法而不是运算符方法)
- 不同的算法
- 不同的数据结构
- 不同的设计模式