0

Here is the code:

template <typename L, typename R> bool eq (const L& lhs, const R& rhs) { return lhs == rhs; }

template<int N> bool eq(char* lhs,           const char(&rhs)[N]) { return String(lhs).compare(rhs) == 0; }
template<int N> bool eq(const char(&lhs)[N], char* rhs)           { return String(lhs).compare(rhs) == 0; }
inline          bool eq(char* lhs,           char* rhs)           { return String(lhs).compare(rhs) == 0; }
inline          bool eq(const char* lhs,     const char* rhs)     { return String(lhs).compare(rhs) == 0; }
inline          bool eq(char* lhs,           const char* rhs)     { return String(lhs).compare(rhs) == 0; }
inline          bool eq(const char* lhs,     char*  rhs)          { return String(lhs).compare(rhs) == 0; }

I have to do this for neq/lt/gt/lte/gte and not just for equality. Maybe I've already missed something.

Is there a way to not list all the possible combinations of C string types?

Also C++98.

EDIT: >> here << is an online demo with the problem

onqtam
  • 4,356
  • 2
  • 28
  • 50
  • 4
    Um, why not just one overload with two `const char*` arguments? – Brian Bi May 16 '16 at 22:19
  • @Brian calling with ```char*``` and ```const char*``` doesn't do the trick if I have an overload only with 2 ```const char*``` - then the template gets called – onqtam May 16 '16 at 22:23
  • Consider using `operator==` – lost_in_the_source May 16 '16 at 22:24
  • @stackptr actually these comparison functions I'm writing are supposed to be used in an expression template that defines the equality operators - like [here](https://github.com/onqtam/doctest/blob/master/doctest/doctest.h#L347) and I want to have a special case for C strings. – onqtam May 16 '16 at 22:25
  • @onqtam: Compiler, version and settings? Because for standard C++, Brian's suggestion is correct and all that is needed. – Benjamin Lindley May 16 '16 at 22:26
  • @BenjaminLindley MSVC 2015, but I will try also with gcc 5.3 – onqtam May 16 '16 at 22:27
  • 1
    @onqtam: Nevermind, I didn't notice you had that template. – Benjamin Lindley May 16 '16 at 22:28
  • 3
    Constrain the first template to reject the case where both decays to `char *` or `const char *`. The relevant SFINAE stuff is implementable in C++03. – T.C. May 16 '16 at 22:41
  • @stackptr You can't overload `==` for non-class types like `char *` and `char *`. `==` for two `char *` pointers compares the pointers as a hard-coded behavior that cannot be overloaded. – Kaz May 16 '16 at 22:53
  • May I ask why you're trying to turn C++ into Java? – uh oh somebody needs a pupper May 16 '16 at 22:58
  • @sleeptightpupper lol - this is gonna be used deep inside my library and the users will use the natural operators for comparison. In a few days you will hear about my library when I release it – onqtam May 16 '16 at 23:00

2 Answers2

4

Decay an array type to pointer:

template<class T>
struct decay_array { typedef T type; };
template<class T, size_t N>
struct decay_array<T[N]> { typedef T* type; };
template<class T>
struct decay_array<T[]> { typedef T* type; };

Check that a type is not a pointer to (possibly const) char:

template<class T>
struct not_char_pointer { enum { value = true }; };
template<>
struct not_char_pointer<char*> { enum { value = false }; };
template<>
struct not_char_pointer<const char*> { enum { value = false }; };

Now check that a type is not a pointer to or array of (possibly const) char:

template<class T>
struct can_use_op : not_char_pointer<typename decay_array<T>::type> {};

Reimplement std::enable_if:

template<bool, class = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> { typedef T type; };

and use it to constrain your template:

template <typename L, typename R> 
typename enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type 
eq (const L& lhs, const R& rhs) { return lhs == rhs; }

Then just one overload is enough:

inline bool eq(const char* lhs, const char* rhs) { return String(lhs).compare(rhs) == 0; }
T.C.
  • 133,968
  • 17
  • 288
  • 421
1
namespace details{
  template<template<class...>class Z,class,class...Ts>
  struct can_apply:std::false_type{};
  template<template<class...>class Z,class...Ts>
  struct can_apply<Z,std::void_t<Z<Ts...>>,Ts...>:std::true_type{};
}
template<template<class...>class Z,class...Ts>
using can_apply=details::can_apply<Z,void,Ts...>;

This tests if a template can be applied some types.

namespace strcmp{
  bool eq(const char*lhs, const char*rhs){/* body */}
}
template<class L, class R>
using str_eq_r=decltype(strcmp::eq(std::declval<L>(),std::declval<R>()));

template<class L, class R>
using can_str_eq=can_apply<str_eq_r,L,R>;

can_str_eq is truthy iff we can call stdcmp::eq on it.

namespace details {
  bool eq(const char* lhs, const char* rhs, std::true_type){
    return strcmp::eq(lhs,rhs);
  }
  template<class L,class R>
  bool eq(L const& l, R const&r,std::false_type){
    return l==r;
  }
}
template<class L,class R>
bool eq(L const& l, R const&r){
  return details::eq(l,r,can_str_eq<L const&,R const&>{});;
}

We could also use a static_if trick to do it inline, if you like:

template<class L,class R>
bool eq(L const& l, R const&r){
  return static_if<can_str_eq>( l, r )(
    strcmp::eq,
    [](auto&& l, auto&& r){return l==r;}
  );
}

After writing a static_if:

template<class...Ts>
auto functor(Ts...ts){
  return [=](auto&& f){
    return f(ts...);
  };
}
namespace details{
  template<class Functor>
  auto switcher(std::true_type, Functor functor){
    return [=](auto&& t, auto&&){
      return functor(t);
    };
  }

  template<class Functor>
  auto switcher(std::false_type, Functor functor){
    return [=](auto&&, auto&& f){
      return functor(f);
    };
  }
}

template<template<class...>class test, class...Ts>
auto static_if(Ts...ts){
  return details::switcher(
    test<Ts...>{},
    functor(ts...)
  );
}

now, what are the odds that works? (Written on phone, not compiled yet) Also not optimal: lots of perfect forwarding, some of which requires de-lamdaing, required.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524