Hooked
Hooked

Reputation: 88198

Template a python-like setattr function

Reading through previous answers, I feel like I may have a design problem, but even if the answer is academic I'd still like to know if it is possible. I've been programming in Python for awhile and it shows. I'm trying to create something like a setattr access on an object. By hand it looks like:

template<class T, class U>
void set_parameter_sigma(T &S, U val) {
  for(auto &x: S) { x.sigma = val; }
}

template<class T, class U>
void set_parameter_epsilon(T &S, U val) {
  for(auto &x: S) { x.epsilon = val; }
}

template<class T, class U>
void set_parameter_length(T &S, U val) {
  for(auto &x: S) { x.length = val; }
}

What I'd like is something that looks like the following pseudocode:

template<class T, class U, class N>
void set_parameter(T &S, U val) {
  for(auto &x: S) { x.N = val; }
}

I could call it like set_parameter(foo, 2.0, "epsilon") and the compiler would create the set_parameter_epsilon function automagically. While I'm sure the boost can do this, I'd prefer to see a STL-only version if possible.

Upvotes: 1

Views: 723

Answers (2)

sehe
sehe

Reputation: 393457

Update:

Oops Turned out I had missed the requirement to loop on container elements inside the setter. Well, then, let me amend my mistake:

#include <utility>

template <class C, class U, class U2 /* assignable from U*/, 
    class T = typename C::value_type>
   void set_parameter(C& container, U&& val, U2 (T::* pmember), typename C::value_type* sfinae=nullptr)
{
    for (auto& instance : container)
        (instance.*(pmember)) = std::forward<U>(val);
}

#include <iostream>
#include <string>
#include <vector>

struct X
{
    double foo;
    std::string splurgle;
};

int main()
{
    std::vector<X> xs(10);

    set_parameter(xs, 42, &X::foo);
    set_parameter(xs, "hello world", &X::splurgle);

    for (auto const& x : xs)
        std::cout << x.foo << ", " << x.splurgle << "\n";
}

Which prints (Live on Coliru)

42, hello world
42, hello world
42, hello world
42, hello world
42, hello world
42, hello world
42, hello world
42, hello world
42, hello world
42, hello world

Original answer text:

#include <utility>
#include <type_traits>

template <class T, class U, class U2 /* assignable from U*/, class T2 = typename std::remove_reference<T>::type>
   T&& set_parameter(T&& instance, U&& val, U2 (T2::* pmember))
{
    (instance.*(pmember)) = std::forward<U>(val);
    return std::forward<T>(instance);
}

This is littered with nuances. But suffice it to say, it "works" as requested:

#include <iostream>
#include <string>
struct X
{
    double foo;
    std::string splurgle;
};

int main()
{
    X x;

    set_parameter(x, 3.14         , &X::foo);
    set_parameter(x, "hello world", &X::splurgle);

    std::cout << x.foo << ", " << x.splurgle;
}

Output:

3.14, hello world

For added insanity: Note that by returning a useful value you can do more ... interesting things, still:

return set_parameter(
        set_parameter(X(), 3.14, &X::foo),
        "hello world", &X::splurgle)
    .splurgle.length();

Upvotes: 5

Tomek
Tomek

Reputation: 4659

You probably can use pointer to member for this but I am not sure if you are going to like that:

struct XX
{
    char c;
    int i;
};


template<class T, class U, class N>
void set_parameter(T &S, U val, N T::*n) {
  for(auto &x: S) { x.*n = val; }
}

set_parameter(..., ..., &XX::c);
set_parameter(..., ..., &XX::i);

Upvotes: 0

Related Questions