在专用模板中添加运算符重载

时间:2013-01-28 15:10:05

标签: c++ templates inheritance operator-overloading

想知道operator ,的有用用法我试图创建一些辅助对象,以便更容易地从C ++代码构建数据库查询。我的想法是按照类似于DB调用的创建指令的顺序利用operator ,。辅助对象如下:

class Fields
{
public:
    Fields &operator ,(const std::string &s)
    {
        SQL.append(s).append(1, ',');
        return *this;
    }

    Fields &operator ,(const Fields &f)
    {
        std::string Result = f;
        SQL.append(Result);
        return *this;
    }

    virtual operator std::string() const = 0;
protected:
    std::string SQL;
};

template <const char *INSTRUCTION> struct Instruction : public Fields
{
    operator std::string() const
    {
        std::string Result(INSTRUCTION);
        return Result.append(SQL);
    }
};

然后,使用正确的typedef和值,此方法允许执行以下操作:

extern const char SQL_SELECT[] = "SELECT ";
extern const char SQL_FROM[] = "FROM ";
extern const char SQL_WHERE[] = "WHERE ";
extern const char SQL_ORDER_BY[] = "ORDER BY ";

typedef Instruction<SQL_SELECT> SELECT;
typedef Instruction<SQL_FROM> FROM;
typedef Instruction<SQL_WHERE> WHERE;
typedef Instruction<SQL_ORDER_BY> ORDER_BY;

std::string Query = ((SELECT(), "a", "b", "c"),
                     (FROM(), "A", "B"),
                     (WHERE(), "a = b AND c <> b"),
                     (ORDER_BY(), "a", "c"));

std::cout << Query;

产生此输出:SELECT a,b,c,FROM A,B,WHERE a = b AND c <> b,ORDER_BY a,c,(我正在处理我的版本中的尾随逗号,为了缩短示例,省略了这部分),here is the code

问题是指令ORDER BY。该指令可以采用改变排序行为的最终操作数,我想将(通过operator ,)枚举值传递给struct Instruction实例:

enum ORDER
{
    ASC,
    DESC,
};

std::string OrderBy = (ORDER_BY(), "a", "c", DESC); // <---- Note the 'DESC' value.

但只想为Instruction<SQL_ORDER_BY>实例启用此运算符,所以我尝试专门化模板:

template <> struct Instruction<SQL_ORDER_BY> : public Fields
{
    Instruction() : order(ASC) {}

    Fields &operator ,(const ORDER o)
    {
        order = o;
        return *this;
    }

    operator std::string() const
    {
        std::string Result(SQL_ORDER_BY);
        Result.append(SQL);
        Result.append(order == ASC? "ASC": "DESC");
        return Result;
    }

private:
    ORDER order;
};

AFAIK此专业化必须有三个operator ,重载:

  • Fields &operator ,(const Fields &)
  • Fields &operator ,(const std::string &)
  • Fields &operator ,(const ORDER)

但是在创建专门化之后,查询字符串:

std::string Query = ((SELECT(), "a", "b", "c"),
                     (FROM(), "A", "B"),
                     (WHERE(), "a = b AND c <> b"),
                     (ORDER_BY(), "a", "c"));

具有值SELECT a,b,c,FROM A,B,WHERE a = b AND c <> b,c,的结尾。这就像忽略ORDER_BY,添加DESC值会导致编译错误:

std::string Query = ((SELECT(), "a", "b", "c"),
                     (FROM(), "A", "B"),
                     (WHERE(), "a = b AND c <> b"),
                     (ORDER_BY(), "a", "c", DESC)); // <-- cannot convert 'ORDER' to 'string'

似乎ORDER值没有进入专门化的operator ,,但在同一名称空间上添加一个空闲运算符可以修复编译错误:

std::string operator ,(const std::string &left, const ORDER right)
{
    std::string Result(left);
    return Result.append(1, ',').append(right == ASC? "ASC": "DESC");
}

但我真的认为Fields &Instruction<SQL_ORDER_BY>::operator ,(const ORDER)会被调用,所以我现在要求一些建议:

  1. 为什么在专门化模板后,Instruction<SQL_ORDER_BY>实例未附加到查询字符串?
  2. 为什么ORDER值未调用专业化提供的Fields &operator ,(const ORDER)
  3. 有多少operator ,Instruction<SQL_ORDER_BY>个实例?
  4. PS:所有这些努力都是为了自学成分,这段代码的几乎零行将最终出现在生产代码中,所以请避免关于使用库或代码实用程序的注释,

    感谢。

    编辑:

    有人删除了他的回答,建议在专业化中添加using Fields::operator std::string;using Fields::operator,;行,修复忽略ORDER_BY 问题。

1 个答案:

答案 0 :(得分:1)

问题是由于,的{​​{1}}子类中Instruction<SQL_ORDER_BY>运算符的重载是隐藏超类的重载运算符。这就是函数调用解析在C ++中的工作方式:名称查找首先发生,一旦找到某个名称空间中的一组名称​​就停止;然后,执行重载决策。

问题在this related article by Herb Sutter中解释。这篇文章与您的问题并不完全相关,但包含了解决方案。特别是,请查看“示例2a ”。

您必须使用Fields指令将using基类的运算符重载导入到派生类的范围内,因此Field,的重载超过了Instruction<SQL_ORDER_BY>隐藏它们。

将这个小程序作为一个简单的例子:

#include <iostream>
#include <string>

using namespace std;

struct A // Class A contains two overloads of operator ,
{
    void operator , (int) { cout << "A::operator , (int)" << endl; }
    void operator , (string) { cout << "A::operator , (string)" << endl; }
};

struct B : A // Class B contains only *one* overload of operator ,
             // Overloads coming from `A` are *hidden* by this one
{
    void operator , (double) { cout << "B::operator , (double)" << endl; }
};

int main()
{
    A a;
    a, 1; // "A::operator , (int)" will be printed to std out
    a, "hello"; // "A::operator , (string)" will be printed to std out

    B b;
    b, 3.0; // "B::operator , (double)" will be printed to the std out
    b, "hello"; // Nothing in the standard output!
}

但是,如果您以这种方式更改B的定义:

struct B : A
{
    using A::operator ,; // <-- Brings A's overloads into scope!
    void operator , (double) { cout << "B::operator , (double)" << endl; }
};

您将看到上面示例程序中main()的最后一行将打印到标准输出:

A::operator , (string)

这意味着B运算符的,重载不再隐藏A中定义的重载,这很可能是您想要的。

<强>更新

另一个问题是答案尚未涵盖。基类,Fields运算符的重载会返回对Fields类型对象的引用。由于,运算符与左侧相关联,因此表达式e1, e2, e3的计算结果为(e1, e2), e3。在您的特定情况下,(e1, e2)的结果是对基类的引用,该基类不支持派生类支持的,运算符的重载。

让我们再次将其简化为一个反映您设计的简单示例:

#include <iostream>
#include <string>

using namespace std;

struct A
{   
    // Operator overloads return a reference to A
    A& operator , (int) 
    { cout << "A::operator , (int)" << endl; return *this; }

    A& operator , (string) 
    { cout << "A::operator , (string)" << endl; *this; }
};

struct B : A
{   
    // Imported overloads still return a reference to A
    using A::operator ,;

    // This overload returns a reference to B 
    B& operator , (double) 
    { cout << "B::operator , (double)" << endl; return *this; }
};

int main()
{
    B b;
    b, 3.0;
    b, "hello", 3.2; // What will be displayed here?
}

考虑示例的最后一行。您可能希望它调用B::operator , (double),但这是打印到标准输出的内容:

A::operator , (int)

为什么呢?好吧,因为逗号运算符和重载的返回类型的关联性。首先,评估表达式b, "hello",并返回A 的引用。然后,在该表达式的结果上,将调用函数A::operator , (3.2)A具有可行的功能,即接受int的功能。而那一个被选中。 B的重载未见,因为第一个表达式b, "hello"的结果属于A&类型。

那怎么解决呢?您可以使用名为CRTP的设计模式(“奇怪的重复模板模式”),并将AB的定义转换为以下内容:

template<typename T>
struct A
{
    T& operator , (int) 
    { cout << "A::operator , (int)" << endl; return *(static_cast<T*>(this)); }

    T& operator , (string) 
    { cout << "A::operator , (string)" << endl; *(static_cast<T*>(this)); }
};

struct B : A<B>
{
    using A::operator ,;
    B& operator , (double) 
    { cout << "B::operator , (double)" << endl; return *this; }
};

这样,上例中main()函数的最后一行将打印出您对标准输出的期望:

A::operator , (string)
B::operator , (double)