滥用逗号运算符

9

我希望你能在编译时轻松构建一个字符串数组。为了测试,我组合了一个名为Strings的类,它具有以下成员:

Strings(); 
Strings(const Strings& that);
Strings(const char* s1);
Strings& operator=(const char* s1);
Strings& operator,(const char* s2);

使用此方法,我可以成功编译类似以下代码的程序:
Strings s;
s="Hello","World!";

s="Hello" 部分调用了 operator=,返回一个 Strings&,然后为 "World!" 调用了 operator,

我无法让它在 MSVC 上工作(还没有尝试其他编译器)。

Strings s="Hello","World!";

我在这里假设Strings s="Hello"会调用复制构造函数,然后一切都会像第一个例子一样运行。但是我收到了错误信息:error C2059: syntax error : 'string' 然而,下面的代码可以正常工作:
Strings s="Hello"; 

我知道复制构造函数至少适用于一个字符串。你有什么想法吗?我真的很希望第二种方法能够工作,以使代码更加简洁。


9
哇,我听过很多关于过载逗号运算符的笑话。我从没想过有人会真的这么做。 - Carl Norum
2
@Carl:你觉得http://www.boost.org/doc/libs/1_40_0/libs/assign/doc/index.html#intro怎么样?这很像OP想要的东西。 - UncleBens
“发抖”自己往棺材里钉钉子。当它开始出问题时,你要如何调试它? - Carl Norum
@Carl:你是指现在吗? :) - Ed S.
我知道重载逗号运算符并不是一个好主意。但我只是想为那些对C++不太熟悉的人创造一个非常友好的API。我曾就人们应该如何正确地学习C++与管理层进行争论,但决定是要为我们的东西创建一个友好的、几乎可脚本化的界面,这只是其中之一,使得人们可以轻松地创建字符串列表(或者任何我需要的东西)。 - miked
2
对于可编程接口,我强烈建议不要使用C++。你可能更好地选择像Lua(http://lua.org)这样的东西,它被*设计*成可编写脚本和嵌入式的。试图使C++更友好的道路会导致深邃而黑暗的地方。 - Greg Hewgill
7个回答

14

我认为你第二个例子中的逗号不是逗号运算符,而是用于多个变量声明的语法元素。

例如,你可以这样写:

int a=3, b=4

我觉得你基本上是在写:

Strings s="Hello", stringliteral
所以编译器期望逗号后的项是一个变量的名称,但它看到一个字符串字面量并宣布一个错误。换句话说,构造函数被应用于"Hello",但逗号之后不是字符串的逗号运算符。
顺便说一下,构造函数实际上不是一个复制构造函数。它从文字字符串参数创建一个Strings对象...复制构造函数这个术语通常适用于同一类型。

3
冒昧啰嗦一句,Strings s="Hello"; 等同于 Strings s = Strings("Hello");。它需要可调用的复制构造函数,并可能甚至会调用它,以从临时对象构造 s。但是,使用 const char* 构造函数对 s 进行直接初始化而不产生任何临时对象的优化是允许且常见的。 - Steve Jessop
呃,真不敢相信我没看到那个。 - miked

8

我不建议使用这种类型的API。因为逗号是优先级最低的运算符,你会继续发现一些不符合预期的情况。例如,即使是下面这个例子也无法正常工作:

if ("Hello","world" == otherStrings) { ... }

如果您每次都使用括号将字符串集合括起来,就可能使事情正常工作,例如:

Strings s=("Hello","World!");

我的示例如下:

if (("Hello","world") == otherStrings) { ... }

这可以实现,但是使用简写语法可能并不值得,因为它带来的复杂语义可能会使事情变得棘手。


3
能不能让这个工作起来?我预见的问题是你无法对const char*重载逗号运算符,因此("Hello", "world")"world"是完全相同的。 - Steve Jessop
我假设你可以重载运算符(const char,const char)。有没有什么原因不能这样做?说实话,我不怎么做C++编程。 - Igor ostrovsky

5

1

这是可以实现的,只要对“实现”有足够宽松的定义。以下是我几年前在回答类似问题时编写的工作示例。这对我来说是一项很有趣的挑战,但我不会在真正的代码中使用它:

#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>

void f0(std::vector<int> const &v) { 
    std::copy(v.begin(), v.end(), 
        std::ostream_iterator<int>(std::cout, "\t"));
    std::cout << "\n";
}

template<class T>
class make_vector {
    std::vector<T> data;
public:
    make_vector(T const &val) { 
        data.push_back(val);
    }

    make_vector<T> &operator,(T const &t) {
        data.push_back(t);
        return *this;
    }

    operator std::vector<T>() { return data; }
};

template<class T> 
make_vector<T> makeVect(T const &t) { 
    return make_vector<T>(t);
}

int main() { 
    f0((makeVect(1), 2, 3, 4, 5));
    f0((makeVect(1), 2, 3));
    return 0;
}

0
您可以使用字符指针数组。
Strings::Strings(const char* input[]);

const char* input[] = {
  "string one",
  "string two",
  0};

Strings s(input);

在构造函数内,迭代指针直到遇到空指针。


或者模板化构造函数:template <int N> Strings(const char* (&input)[N]) { for (int i = 0; i < N; ++i) push_back(input[i]); }。然后不需要空终止符:你可以这样做 const char *input[] = { "this", "that"}; Strings s(input);。不确定它是否真的值得,但如果你喜欢这种东西,那就有趣了。 - Steve Jessop
哦,当然构造函数模板也可以调用类似于 vector::insert(iterator, InputIterator, InputIterator) 的另一个函数,以避免在不同位置使用不同长度时生成重复的代码。 - Steve Jessop

0
如果你使用c++0x,它们为此提供了新的初始化列表!我希望你能使用它们。例如:
std::vector<std::string> v = { "xyzzy", "plugh", "abracadabra" };
std::vector<std::string> v{ "xyzzy", "plugh", "abracadabra" };

0
如果 `Strings` 类型仅用于存储字符串列表的话,我认为使用标准容器加上 `boost::assign` 工具会更加适合 :)
using namespace boost::assign;
vector<string> listOfThings;
listOfThings += "Hello", "World!";

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接