0

I have next command:

auto result = Builder::get(
        ObjKey("key1"),
        ObjVal("test1"),
        (ObjKey("key2"), ObjVal("test21"), (ObjKey("key3"), ObjVal("test31"), ObjVal("test32")), ObjVal("test22")),
        ObjVal("test2")
    );

I expect to get the following result:

{key1, test1, {key2, test21, {key3, test31, test32}, test22}, test2}

but I get this result:

{key1, test1, test22, test2}

The program code is below (standard 11):

#include <iostream>

class ObjKey {
public:
    ObjKey() = delete;

    ObjKey(const std::string& key) : m_key(key) { }

    std::string get() const {
        return m_key + ", ";
    }

private:
    std::string m_key;
};

class ObjVal {
public:
    ObjVal() = delete;

    ObjVal(const std::string& value) : m_value(value) { }

    std::string get() const {
        return m_value + ", ";
    }

private:
    std::string m_value;
};

class Builder {
public:
    template<typename... Args>
    static std::string get(ObjKey&& objKey, Args&& ...args) {
        std::string resultValue;
        get(resultValue, std::forward<ObjKey>(objKey), std::forward<Args>(args)...);
        return resultValue;
    }

private:
    Builder() {}

    template<typename... Args>
    static void get(std::string& resultValue, ObjKey&& objKey, Args&& ...args) {
        resultValue.append("{");
        resultValue.append(objKey.get());
        std::string values;
        get(values, std::forward<Args>(args)...);
        resultValue.append(values);
        resultValue.append("}");
    }

    template<typename... Args>
    static void get(std::string& resultValue, ObjVal&& objVal, Args&& ...args) {
        resultValue.append(objVal.get());
        get(resultValue, std::forward<Args>(args)...);
    }

    static void get(std::string& resultValue) {}
};

int main()
{
    auto result = Builder::get(
        ObjKey("key1"),
        ObjVal("test1"),
        (ObjKey("key2"), ObjVal("test21"), (ObjKey("key3"), ObjVal("test31"), ObjVal("test32")), ObjVal("test22")),
        ObjVal("test2")
    );
    std::cout << result << "\n";
}

What am i doing wrong?

And is it possible to solve with templates?

max66
  • 65,235
  • 10
  • 71
  • 111
Ivan
  • 1
  • 1
  • `(ObjKey("key2"), ObjVal("test21"), (ObjKey("key3"), ObjVal("test31"), ObjVal("test32")), ObjVal("test22")))` what do you think the value of this expression is? – n. m. could be an AI Nov 04 '18 at 15:24
  • It is tree: ObjKey("key1") ObjVal("test1"), ObjKey("key2") ObjVal("test21") ObjKey("key3") ObjVal("test31") ObjVal("test32") ObjVal("test22") ObjVal("test2") – Ivan Nov 04 '18 at 19:44
  • I hope you understand it isn't a tree. Nothing in your code creates trees. I would recommend making a Tree class. – n. m. could be an AI Nov 04 '18 at 23:34

1 Answers1

0

What am i doing wrong?

As suggested by n.m., you have to consider what the comma operator do to the following expression

(ObjKey("key2"), ObjVal("test21"), (ObjKey("key3"), ObjVal("test31"), ObjVal("test32")), ObjVal("test22"))

The comma operator discard all values but the last, so the expression become

(ObjVal("test22"))

And is it possible to solve with templates?

Yes, but the best I can imagine involve std::tuple.

I mean... if you accept that the get() call become

   auto result = Builder::get(
      ObjKey("key1"),
      ObjVal("test1"),
      std::make_tuple(ObjKey("key2"), ObjVal("test21"),
                      std::make_tuple(ObjKey("key3"), ObjVal("test31"),
                                      ObjVal("test32")),
                      ObjVal("test22")),
      ObjVal("test2"));

so with a std::make_tuple before the grouping (, you can add a couple of private get() to manage the tuple case.

So, renaming getH() (for "get helper") the private versions of get(), the tuple-managing versions of getH() can be written as follows (a C++14 solution, because use std::index_sequence and std::make_index_sequence; but if you need a C++11 solution, isn't difficult to write C++11 substitutes)

  template <std::size_t ... Is, typename ... Ts, typename ... As>
  static void getH (std::string & rV, std::index_sequence<Is...> const &,
                    std::tuple<Ts...> const t, As ... as)
   { 
     getH(rV.append(", "), std::get<Is>(t)...);
     getH(rV, as...);
   }

  template <typename ... Ts, typename ... As>
  static void getH (std::string & rV, std::tuple<ObjKey, Ts...> const t,
                    As ... as)
   { getH(rV, std::make_index_sequence<1u+sizeof...(Ts)>{}, t, as...); }

The key and value versions are managed as follows

  template <typename ... As>
  static void getH (std::string & rV, ObjKey const & oK, As ... as)
   {
     getH(rV.append(1u, '{').append(oK.get()), as...);
     rV.append(1u, '}');
   }

  template <typename ... As>
  static void getH (std::string & rV, ObjVal const & oV, As ... as)
   { getH(rV.append(", ").append(oV.get()), as...); }

and, obviously, the ground case

  static void getH (std::string &)
   { }

The public get() version become

  template <typename ... As>
  static std::string get (As ... as)
   {
     std::string resultValue;
     getH(resultValue, as...);
     return resultValue;
   }

Observe that I've transfered the management of the comma from ObjKey and ObjVal to getH() and that I've removed the forwarding semantics (you never used std::move).

The following is a full C++14 example

#include <tuple>
#include <iostream>

class Obj
 {
   public:
      Obj() = delete;

      Obj (std::string const & v0) : v{v0}
       { }

      std::string const & get () const
       { return v; }

   private:
      std::string  v;
 };

struct ObjKey : public Obj
 { ObjKey (std::string const & v0) : Obj{v0} { } };

struct ObjVal : public Obj
 { ObjVal (std::string const & v0) : Obj{v0} { } };

class Builder
 {
   private:
      template <std::size_t ... Is, typename ... Ts, typename ... As>
      static void getH (std::string & rV, std::index_sequence<Is...> const &,
                        std::tuple<Ts...> const t, As ... as)
       { 
         getH(rV.append(", "), std::get<Is>(t)...);
         getH(rV, as...);
       }

      template <typename ... Ts, typename ... As>
      static void getH (std::string & rV, std::tuple<ObjKey, Ts...> const t,
                        As ... as)
       { getH(rV, std::make_index_sequence<1u+sizeof...(Ts)>{}, t, as...); }

      template <typename ... As>
      static void getH (std::string & rV, ObjKey const & oK, As ... as)
       {
         getH(rV.append(1u, '{').append(oK.get()), as...);
         rV.append(1u, '}');
       }

      template <typename ... As>
      static void getH (std::string & rV, ObjVal const & oV, As ... as)
       { getH(rV.append(", ").append(oV.get()), as...); }

      static void getH (std::string &)
       { }

   public:
      template <typename ... As>
      static std::string get (As ... as)
       {
         std::string resultValue;
         getH(resultValue, as...);
         return resultValue;
       }
 };

int main()
 {
   auto result = Builder::get(
      ObjKey("key1"),
      ObjVal("test1"),
      std::make_tuple(ObjKey("key2"), ObjVal("test21"),
                      std::make_tuple(ObjKey("key3"), ObjVal("test31"),
                                      ObjVal("test32")),
                      ObjVal("test22")),
      ObjVal("test2"));

   std::cout << result << "\n";
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • I can not use C+14 and I can not make a more harder request (according to the requirements): auto result = Builder::get(.... – Ivan Nov 04 '18 at 19:54
  • @Ivan - for a C++11 solution, you can use a C++11 replacement for `std::make_index_sequence` and `std::index_sequence`; I suggest the following [question and answers](https://stackoverflow.com/questions/17424477/implementation-c14-make-integer-sequence/17426611#17426611); for the not-more-harder-request... I don't understand what do you mean. – max66 Nov 04 '18 at 20:19