Adding an operator overload in a custom template

While exploring useful uses operator ,

, I'm trying to create a small set of helper objects to make it easier to build database queries from C ++ code. My idea is to use operator ,

in order of writing instructions that resemble DB calls. The auxiliary objects are as follows:

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);
    }
};

      

Then, with the correct typedef

values ​​and values, this approach allows you to do the following:

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;

      

Which produces this output: SELECT a,b,c,FROM A,B,WHERE a = b AND c <> b,ORDER_BY a,c,

(I care about trailing commas in my version, this part is omitted to shorten the example), here is the code .

The problem lies in the instructions ORDER BY

. This command can accept a final operand that changes the ordering behavior, I want to pass (via operator ,

) the enum value to the instance struct Instruction

:

enum ORDER
{
    ASC,
    DESC,
};

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

      

But you only need to include this operator for instances Instruction<SQL_ORDER_BY>

, so I tried to specialize the template:

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 this specialization should have three overloads operator ,

:

  • Fields &operator ,(const Fields &)

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

    ...
  • Fields &operator ,(const ORDER)

    ...

But after creating the specialization, the query string is:

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

      

End having a value of: SELECT a,b,c,FROM A,B,WHERE a = b AND c <> b,c,

. It looks like if ORDER_BY

ignored and adding a value DESC

results in a compile error:

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'

      

It seems that the values ​​are ORDER

not part of the operator ,

specialization, but adding a free operator to the same namespace fixes a compilation error:

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

      

But I really thought there would be Fields &Instruction<SQL_ORDER_BY>::operator ,(const ORDER)

, so I now ask for advice:

  • Why are Instruction<SQL_ORDER_BY>

    n't instances being added to the query string after template specialization?
  • Why are the values ORDER

    not being called Fields &operator ,(const ORDER)

    provided by the specialization.
  • How many operator ,

    copies Instruction<SQL_ORDER_BY>

    do you have ?

PS: All this effort is for autodidact purposes, almost zero lines of this code will be included in production code, so please avoid comments about using libraries or about utility code.

thank.

EDIT:

Someone who deleted my answer, suggested to add using Fields::operator std::string;

, and using Fields::operator,;

lines in the specialization, correcting ignoring the problem ORDER_BY

.

+3


source to share


1 answer


The problem stems from the fact that your operator overloading ,

in the subclass is Instruction<SQL_ORDER_BY>

Fields

hiding the overloaded operators from the superclass. This is just how the call invocation function works in C ++: the name lookup happens first and stops as soon as a set of names in a specific namespace is found; then overload resolution is performed.

The problem is explained in this linked article by Herb Sutter . This article is not entirely related to your problem, but contains a solution. In particular, check " Example 2a ".

You have to use the directive using

to import the base class overload operators Field

in the area of the derived classes, so your overload ,

in Instruction<SQL_ORDER_BY>

not hide them.

Take this little program as a simple example:

#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!
}

      

However, if you change the definition B

as follows:

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

      

You will see that the last line main()

in the example program above will print it to standard output:

A::operator , (string)

      

This means that B

operator overloading ,

no longer hides the overloads defined in A

, which is most likely what you want.

UPDATE:



There is another problem that the answer has not yet addressed. Operator overloading ,

in the base class Fields

returns a reference to an object of the type Fields

. Since the operator is ,

bound to the left, the expression e1, e2, e3

evaluates to (e1, e2), e3

. In your specific case, the result (e1, e2)

is a reference to a base class that does not support operator overloading ,

, which is supported by the derived class.

Let's boil it down again to a simpler example that reflects your design:

#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?
}

      

Let's look at the last line of the example. You probably expect it to call B::operator , (double)

, but this is what is printed to standard output:

A::operator , (int)

      

Why? Well, because of the associativity of the comma operator and the return type of your overloads. First, the expression b, "hello"

is evaluated and returns a reference toA

. Then the function will be called as a result of this expression A::operator , (3.2)

. A

has a viable function that takes on meaning int

. And he gets out. B

no overloading is observed because the result of the first expression b, "hello"

is of type A&

.

So how to solve it? You can use the CRTP pattern design ( "Curiously Recurring Template Pattern") and turn the definition A

and B

in the following:

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; }
};

      

So the last line of the function main()

in the above example will print what you expect from standard output:

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

      

+1


source







All Articles