0

I'm designing a library for internal use.

A function can be

template<typename It>
void doStuff(It begin, It end)
{
    // This is example code. The point is to show that I access the data of the iterator
    doStuffInternal(it->a, it->b, it->c);
}

This function is a template because I want to accept all kind of iterators, but I have specific expectations on the type that this iterators produce.

At the moment my code assumes an object is passed with a structure like

struct A
{
      int a;
      std::string b;
      BigObject   c;
};

I know the calling code of this function will receive data from an external API, and the data will look something like

struct AlmostA
{
    int a_;
    std::string _b;
    AlmostBigObject   cc;
};

Now I can't pass this AlmostA to my function and I need to convert it to A (or something that behaves like A), even if all the information are in AlmostA, just with different names (and slightly different types).

What I'm thinking about doing is to create a function to access the fields

inline int getA(const &A a)
{
    return a.a;
}

inline std::string& getB(const &A a)
{
    return a.b;
}

and so on for every field I need to access, then rewrite my function to be

template<typename It>
void doStuff(It begin, It end)
{
    doStuffInternal(getA(*it), getB(*it), getC(*it));
}

Then the calling code can define

inline int getA(const &AlmostA a)
{
    return a.a_;
}

inline std::string& getB(const &AlmostA a)
{
    return a._b;
}

and call my function with an iterator of AlmostA without any conversion.

What I hope to achieve with this is that the calling code can define how they provide the information, without being forced to have a structure with those specific fields.

I googled around and couldn't find any example of code doing this.

I'm relatively new to C++, so I'd like if this would work, what are the pitfalls of this approach, why is it not popular or not used (I know something kind of similar is done with std::swap, but that's a particular function) what are alternative solutions to present data with different interface in a unified way in the C++ world?

In what namespace does the getter function need to be implemented in order for the compiler to find them?

Makers_F
  • 3,033
  • 4
  • 36
  • 51
  • if what you want to do is a for each loop, you can use [std::for_each](http://en.cppreference.com/w/cpp/algorithm/for_each) (maybe with lambda) – apple apple Jan 15 '17 at 16:20
  • What is inside the function is irrelevant, that is only an example. I'm going to do operation with that data, the question is about how to provide an access to that data in a generic way – Makers_F Jan 15 '17 at 16:24
  • Why can't you pass `AlmostA` to your function? – Unimportant Jan 15 '17 at 16:26
  • @Makers_F Any generic interface/algorithm must have some requirements or `concepts`. In your case, would it be sufficient to constrain the use of `doStuff ` function to take an `iterator` whose `value_type` can be passed to `getA`, `getB` and `getC` functions AND has stream `operator <<` defined ? – Arunmu Jan 15 '17 at 16:27
  • Because AlmostA doesn't have a field named `a`, and thus will not even compile – Makers_F Jan 15 '17 at 16:28
  • What if someday you want `SetA()`? or get address of `A`? – apple apple Jan 15 '17 at 16:30
  • @Arunmu my only constraint is that I'm able to get the information from the struct. The calling code might get the information from an external library it can not modify, so with the `getA` they could just provide the implementation of the function, but if I do `it->a` they will have to create a struct which has the specific interface I expect. – Makers_F Jan 15 '17 at 16:31
  • @appleapple good point! I only need to read the data for the foreseeable future. But in case I needed to `SetA` do you think this approach could be extended to provide that? – Makers_F Jan 15 '17 at 16:33
  • 1
    If the fields of the structure are not in your control, then I don't see anything wrong in general with your proposed approach. You can use `boost::fusion` to give the design a better boost (no pun intended), but as you say you are new to C++, I would probably not recommend it at this point of time. – Arunmu Jan 15 '17 at 16:38
  • @Makers_F of course the approach can be extended to do (almost) everything, but I would recommend keep it simple. BTW, your `doStuff` now can not compile. – apple apple Jan 15 '17 at 16:46
  • @Arunmu Basically it works like this: Layer 1( I don't control this) -- sends data in structs layer 1 defines --> Layer 2 (I do control this) --- calls ---> Layer 3 (this library I'm writing. I do control this one as well). Layer 2 could transform every struct it gets from Layer 1 to pass it to Layer 3, but that would involve copying stuff around. What's your opinion about transforming data vs overloading functions to abstract over the data? Do you think this approach might bite back in the future? I'll check boost::fusion – Makers_F Jan 15 '17 at 16:48
  • @Makers_F I would prefer overloading functions instead of transforming data. OR you could provide both options to the user as you are developing a library. – Arunmu Jan 15 '17 at 17:17

1 Answers1

1

Your doStuffInternal(getA(*it), getB(*it), getC(*it)) seems solid to me - I would use a struct template with an explicit specialization for every type that you need to support.

template <typename T>
struct adapter;

template <>
struct adapter<A>
{
    template <typename T>
    decltype(auto) a(T&& x) { return forward_like<T>(x.a); }

    template <typename T>
    decltype(auto) b(T&& x) { return forward_like<T>(x.b); }

    // ...
};

template <>
struct adapter<AlmostA>
{
    template <typename T>
    decltype(auto) a(T&& x) { return forward_like<T>(x.a_); }

    template <typename T>
    decltype(auto) b(T&& x) { return forward_like<T>(x._b); }

    // ...
};

Using decltype(auto) as the return type and forward_like allows you to preserve the value category of x's members:

static_assert(std::is_same<decltype(adapter<A>::a(A{})), int&&>{});

A lvalue{};
static_assert(std::is_same<decltype(adapter<A>::a(lvalue)), int&>{});

const A const_lvalue{};
static_assert(std::is_same<decltype(adapter<A>::a(const_lvalue)), const int&>{});

wandbox example (of the value category propagation)


The final code will look something like this:

template<typename It>
void doStuff(It begin, It end)
{
    adapter<std::decay_t<decltype(*it)>> adp;
    doStuffInternal(adp.a(*it), adp.b(*it), adp.c(*it));
}

In C++11, you need to explicitly specify the return type using a trailing return type. Example:

template <typename T>
auto a(T&& x) -> decltype(forward_like<T>(x.a_))
{ 
    return forward_like<T>(x.a_); 
}
Community
  • 1
  • 1
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • What are the advantages of putting the code into a specialized struct? – Makers_F Jan 15 '17 at 17:19
  • @Makers_F: no, you need to explicitly specify the return type with a *trailing return type*. I'll edit my question – Vittorio Romeo Jan 15 '17 at 17:19
  • @Makers_F: the advantages are that all getters for a specific type are "grouped together" and that you have a common type for all adapters. This can allow you to define a "concept" in the future that statically checks at compile-time whether or not a particular specialization of `adapter` has all the getters you require. – Vittorio Romeo Jan 15 '17 at 17:20
  • I see. So I could enable_if ? – Makers_F Jan 15 '17 at 17:22
  • 1
    @Makers_F: exactly. You could have a compile-time boolean `is_valid_adapter` where you check that `TAdapter` implements all the required getters. Then you could either use `static_assert` or `std::enable_if` with `is_valid_adapter` to produce a nicer error message when you forgot to implement one of the getters in `TAdapter`. Another advantage is that you could also use inheritance to have some sort of "fallback" adapter type or to avoid duplication for types that have the same name for some fields. – Vittorio Romeo Jan 15 '17 at 17:25