Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function template won't work with string literals

Background:

I'm working on a query DSL that'll parse expressions with ==, <, etc., and return filter objects via operator overloading.

Problem:

My template method is failing when used with a string literal. I've tried providing specific instantiations of the template taking both std::string, and char but neither seems to work.

The code is below. The line causing the problem in main is marked with a comment. Alternative solutions I've tried are commented out within the code.

A runnable repl of the same code can be found here.

I do know that manually wrapping the string literal with std::string("text") will work, but I want to be able to use plain string literals, if at all possible.

#include <iostream>

template<typename T>
struct Filter;

struct Field
{
    Field(const std::string &val): name(val) { }
    Field(std::string &&val): name(std::move(val)) { }
    std::string name;

    // template <signed N>
    // Filter<std::string> operator==(const char (&val) [N]);

    template <typename T>
    Filter<T> operator==(const T &val);
};

template <typename T>
Filter<T> Field::operator==(const T &val)
{
    return Filter<T>{ *this, val, "==" };
}

// template <signed N>
// Filter<std::string> Field::operator==(const char (&val) [N])
// {
//   return Filter<std::string>{ *this, std::string(val), "==" };
// }

// template <>
// Filter<std::string> Field::operator==<std::string>(const std::string &val)
// {
//     return Filter<std::string>{ *this, val, "==" };
// }


template<typename T>
struct Filter
{
    Field f;
    T val;
    std::string op;
};

int main() {
  Field f1 { "field1" };
  Field f2 { "field1" };
  std::cout << (f1 == 1).val;
  std::cout << (f1 == "Hello").val;  // <--- the source of my problems
}
like image 389
Adam Rackis Avatar asked Jul 15 '19 16:07

Adam Rackis


Video Answer


3 Answers

Issue is that c-array is not copyable, so

Filter<char [6]>{ *this, val, "==" }; // Error

Your overload is correct, but Filter needs to reordered and defined before your operator== overload. The overload returning Filter<T> is dependent on T, and so in that case Filter's definition can be postponed. But when you return Filter<std::string> the compiler needs an actual definition of Filter up front.

#include <iostream>

template<typename T>
struct Filter;

struct Field
{
    Field(const std::string &val): name(val) { }
    Field(std::string &&val): name(std::move(val)) { }
    std::string name;

    template <std::size_t N> Filter<std::string> operator==(const char (&val) [N]);

    template <typename T>
    Filter<T> operator==(const T &val);
};

template<typename T>
struct Filter
{
    Field f;
    T val;
    std::string op;
};

template <typename T>
Filter<T> Field::operator==(const T &val)
{
    return Filter<T>{ *this, val, "==" };
}

template <std::size_t N>
Filter<std::string> Field::operator==(const char (&val) [N])
{
   return Filter<std::string>{ *this, std::string(val), "==" };
}

int main() {
  Field f1 { "field1" };
  Field f2 { "field1" };
  std::cout << (f1 == 1).val;
  std::cout << (f1 == "Hello").val;
}

Demo

like image 197
Jarod42 Avatar answered Sep 20 '22 03:09

Jarod42


What you can do is specialize Filter for the case when T gets deduced to a char[N]. Adding

template<std::size_t N>
struct Filter<char[N]>
{
    Field f;
    std::string val;
    std::string op;
};

Will cause Filter<T>{ *this, val, "==" } to call the above specialization and it will use a std::string to store val.

like image 24
NathanOliver Avatar answered Sep 17 '22 03:09

NathanOliver


Since you have the C++17 tag, here is yet another option to solve this problem: deduction guides

#include <iostream>

template<typename T>
struct Filter;

struct Field
{
    Field(const std::string &val): name(val) { }
    Field(std::string &&val): name(std::move(val)) { }
    std::string name;

    // note the use of auto here
    template <typename T>
    auto operator==(const T &val);
};

template <typename T>
auto Field::operator==(const T &val)
{
    // do not use Filter<T> here, or the deduction guide won't kick in
    return Filter{ *this, val, "==" };
}

template<typename T>
struct Filter
{
    Field f;
    T val;
    std::string op;
};

// ------- Deduction Guides -----------
template<typename T>
Filter(Field, T, std::string) -> Filter<T>;

// will tell the compiler to create a Filter<string> with a c-array argument
template<std::size_t N>
Filter(Field, const char(&)[N], std::string) -> Filter<std::string>;
// ------------------------------------

int main() {
  Field f1 { "field1" };
  Field f2 { "field1" };
  std::cout << (f1 == 1).val;

  // creates a Filter<string> instead of trying to 
  // create a Filter<const char(&)[6]> due to the deduction guide
  std::cout << (f1 == "Hello").val;
}
like image 36
Velocirobtor Avatar answered Sep 20 '22 03:09

Velocirobtor