3

(I'm using catch for unit testing, which unfortunately doesn't yet have what it's calling generators to do this sort of thing.)

In c++17, is there a way to reduce this:

assert(""  == String{""  }.removeLeading(' '));
assert("a" == String{" a").removeLeading(' '));
assert("a" == String("a" }.removeLeading(' '));
assert("a" == String{"a "}.removeLeading(' '));

With a macro, template, or function like this:

#define MACRO(className, method, arg, ...) \
   for(auto [x, y] : { __VA_ARGS }) { \
      assert(x == className{y}.method(arg)); \
   }

So it's shorter like this:

MACRO(String, removeLeading, ' ',
   { "", "" }, {"a", " a"}, {"a", "a"}, {"a", "a "})

// or

MACRO(String, removeLeading, ' ',
   "", "",    "a", " a",    "a", "a",    "a", "a ")

Assuming that all ... arguments "auto" to the same type.

Basically with no limit on the number of ... args. (Maybe could go up to 100?)

Using the first MACRO() gives: unable to deduce 'std::initializer_list<auto>&&' from... (it also doesn't understand the semantics so strictly breaks apart on , tokens, but as long as it's put together correctly, doesn't matter.)

Using the second MACRO() gives: cannot decompose non-array non-class type 'const char*'


Trying templates:

template<typename T>
void TEMP(T a, T b) {
    assert(a == String{ b }.removeLeading(' '));
}

template<typename T, typename... Args>
void TEMP(T a, T b, Args... args) {
    TEMP(a, b);
    TEMP(args...);
}

TEMP("", "",    "a", " ",    "a", "a",    "a", "a ");

This at least works, but I don't want className, method, and arg to be hardcoded as "String", "removeLeading", and " ".


I wonder if there's a way to pull this off with all the new type trait / "meta" templates (not sure what else to call them) that I haven't done much with. (I look at some of the libraries available in the last year or two, and they almost look like a different language to me...)

max66
  • 65,235
  • 10
  • 71
  • 111
user1902689
  • 1,655
  • 2
  • 22
  • 33

2 Answers2

2

This very nearly works:

for (auto [x,y] : {{"a", "b"}, {"c", "d"}, {"e", "f"}}) {
    foo(x, y);
}

The only problem is that the inner braced-init-lists can't be deduced themselves. So we just need to give the compiler a little push.

using P = std::pair<char const*, char const*>;
for (auto [x,y] : {P{"a", "b"}, {"c", "d"}, {"e", "f"}}) {
    foo(x, y);
}

And that works. Just identifying the first one is sufficient. Or, for your specific example:

for (auto [exp, arg] : {P{"", ""}, {"a", " a"}, {"a", "a"}, {"a", "a "}}) {
    assert(exp == String(arg).removeLeading(' '));
}

If you really want to write a macro for that, then it should be clear to see how to do that at this point.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Works great in clang and gcc. VS2017 15.5 Preview gives `error C3312: no callable 'begin' function found for type 'initializer list'` as well as `'end'`. My original post only specified `c++17`, didn't mention VS2017, and of course VS2017 doesn't have all of it implemented yet. This might be P0017R1. Do you know of a workaround until Visual Studio catches up to the rest of the world? – user1902689 Oct 25 '17 at 21:19
  • @user1902689 This answer doesn't that feature - the only C++17 feature here is structured bindings. You could instead try `std::initializer_list

    {{"", ""}, {"a", " a"}, ...}`?

    – Barry Oct 25 '17 at 21:34
  • On clang and gcc, `std::pair` can even be used, for anything that can be used with any, to drop having to specify the parameter types. – user1902689 Oct 25 '17 at 21:49
  • Yes, VS takes this, with `pair`. `pair` causes VS2017 15.5 Preview to give an internal compiler error. I'll file a bugreport on that. I'll also file one for the original `C3312`, since structured bindings is listed as fully implemented. – user1902689 Oct 25 '17 at 21:52
0

I don't want className, method, and arg to be hardcoded as "String", "removeLeading", and " ".

If I understand correctly what do you want, the following struct foo (with static methods func()) could give you an idea (but you have to fine tune details)

template <typename C, auto ... Vs>
struct foo
 {
   template <typename R> 
   static void func (R(C::*)(decltype(Vs) const & ...))
    { }

   template <typename R, typename T1, typename T2, typename ... Args>
   static void func (R(C::*m)(decltype(Vs) const & ...),
                     T1 const & a, T2 const & b, Args const & ... args)
    {
      assert( a == (C{b}.*m)(Vs...) );

      func(m, args...);
    }
 };

The following is a full compiling example (fail running because removeLeading() isn't really implemented; so abort() are failing)

#include <cassert>
#include <string>

// fake String strict, with fake removeLeading() method, to
// demonstrative/compiling purposes 
struct String
 {
   String (std::string const &) 
    { }

   std::string removeLeading (char const &)
    { return { }; }
 };

template <typename C, auto ... Vs>
struct foo
 {
   template <typename R> 
   static void func (R(C::*)(decltype(Vs) const & ...))
    { }

   template <typename R, typename T1, typename T2, typename ... Args>
   static void func (R(C::*m)(decltype(Vs) const & ...),
                     T1 const & a, T2 const & b, Args const & ... args)
    {
      assert( a == (C{b}.*m)(Vs...) );

      func(m, args...);
    }
 };

int main()
 {
   foo<String, ' '>::func(&String::removeLeading,
      "", "",    "a", " ",    "a", "a",    "a", "a ");
 }

-- EDIT --

The OP compiler doesn't support the auto type for template not-type arguments.

So the preceding solution (strongly auto based) can't work.

I propose another solution, C++14 compatible, that should work.

#include <tuple>
#include <string>
#include <cassert>
#include <functional>

// fake String strict, with fake removeLeading() method, to
// demonstrative/compiling purposes 
struct String
 {
   String (std::string const &) 
    { }

   std::string removeLeading (char const &)
    { return { }; }
 };

template <typename C, typename ... Vs, std::size_t ... Is, typename R>
void barH (std::tuple<Vs...> const &, std::index_sequence<Is...> const &,
           R(C::*)(Vs const & ...))
 { }

template <typename C, typename ... Vs, std::size_t ... Is, typename R,
          typename T1, typename T2, typename ... Args>
void barH (std::tuple<Vs...> const & tv,
           std::index_sequence<Is...> const & is, R(C::*m)(Vs const & ...),
           T1 const & a, T2 const & b, Args const & ... args)
 {

   assert( a == (C{b}.*m)(std::get<Is>(tv)...) );

   barH<C>(tv, is, m, args...);
 }

template <typename C, typename ... Vs, typename R, typename ... Args>
void bar (std::tuple<Vs...> const & tv, R(C::*m)(Vs const & ...),
          Args const & ... args)
 { barH<C>(tv, std::make_index_sequence<sizeof...(Vs)>{}, m, args...); }


int main()
 {
   bar<String>(std::make_tuple(' '), &String::removeLeading,
               "", "",    "a", " ",    "a", "a",    "a", "a ");
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • I like both of these solutions. The other is much simpler, but I like this one better because it doesn't require specifying the in and out argument types. No idea why someone downvoted it. – user1902689 Oct 25 '17 at 21:31
  • Works great in clang and gcc. VS2017 15.5 Preview gives errors `C3533: a parameter cannot have a type that contains 'auto'`, `C2672: no matching overloaded function found`, `C2784: could not deduce template argument`, and `C2780; expects 1 arguments - 9 provided`. My original post only specified `c++17`, didn't mention VS2017, and of course VS2017 doesn't have all of it implemented yet. This might be PR0091R3 and P0127R2. Do you know of a workaround until Visual Studio catches up to the rest of the world? – user1902689 Oct 25 '17 at 21:31
  • @user1902689 - loosing `auto` for the type of template values you loose the real advantage of this solution: two variadic list of arguments, one for the `struct` and one for the method. So, the best I can imagine is to wrap one list in a `std::tuple`; non so elegant as in C++17 but (I think) possible. Give me some minutes... – max66 Oct 25 '17 at 21:39
  • @user1902689 - one moment: a question: your VS2017 15.5 support `std::apply`? – max66 Oct 25 '17 at 21:41
  • @user1902689 - unfortunately I don't know how to apply `std::apply` to a method. Well... there is the good old method based on `std::get` and `std::index_sequence`. Answer improved adding a C++14 solution. – max66 Oct 25 '17 at 22:15
  • The `c++14` solution works in VS2017 15.5 Preview, gcc, and clang. Thanks for the revision! – user1902689 Oct 25 '17 at 22:35