TobiMcNamobi
TobiMcNamobi

Reputation: 4823

Avoiding define macro when making a lot of similar method calls

The code below uses the #define macro. I'd like to avoid this but I don't see a way that uses C++ features and is more readable. The code is for testing a class ClassUnderTest that merges two instances of PoorLegacyClass into one. This PoorLegacyClass has many getters and setters and for testing purposes I need to call a lot of setters (or do I?).

In short, instead of writing

a.setValueX(20);
b.setValueX(30);

I would like to write something like

set(a, b, ValueX, 20, 30);
// or
set<ValueX>(a, 20, b, 30);
// or even
set(a, b, &PoorLegacyClass::setValueX, 20, 30);

Here is code similar to the code I use at the moment:

#include <boost/test/unit_test.hpp>

#include "ClassUnderTest.h"

BOOST_AUTO_TEST_SUITE(Test_ClassUnderTest);

#define SETTER_A_B(fieldname, valueA, valueB)\
        a.set##fieldname(valueA);\
        b.set##fieldname(valueB);

struct TestContext
{
    PoorLegacyClass a, b;
};

BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description1, TestContext)
{
    SETTER_A_B(ValueX, 20, 30);
    auto result = ClassUnderTest().merge(a, b);

    BOOST_TEST(20 == result.getValueX());
}

BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description2, TestContext)
{
    SETTER_A_B(ValueY, 21, 37);
    auto result = ClassUnderTest().merge(a, b);

    BOOST_TEST(21 + 37 == result.getValueY());
}

BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description3, TestContext)
{
    SETTER_A_B(ValueZ, 12, 83);
    auto result = ClassUnderTest().merge(a, b);

    BOOST_TEST(12 + 83 == result.getValueZ());
}

BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description4, TestContext)
{
    SETTER_A_B(ValueY, 212, 37);
    SETTER_A_B(ValueX, 20, 30);
    auto result = ClassUnderTest().merge(a, b);

    BOOST_TEST(a.getValueY() == result.getValueY());
}

// more test cases that are similar to those above

BOOST_AUTO_TEST_SUITE_END();

While I will refactor PoorLegacyClass in the future that is not the subject of this question. What I would like to know, though, is how to avoid using the #define macro.

Upvotes: 1

Views: 102

Answers (3)

Since you're willing to accept the syntax set(a, b, &PoorLegacyClass::setValueX, 20, 30);, implementing such a set should be easy:

template <class T>
void set(PoorLegacyClass &a, PoorLegacyClass &b, void (PoorLegacyClass::*setter)(T), T valForA, T valForB)
{
  (a.*setter)(valForA);
  (b.*setter)(valForB);
}

You could also make it so that the valFor parameters use T in a non-deduced context, so that T can only be deduced from the setter and implicit conversions happen on the values:

template <class T>
struct NonDeduced { using type = T; }

template <class T>
void set(PoorLegacyClass &a, PoorLegacyClass &b, void (PoorLegacyClass::*setter)(T), typename NonDeduced<T>::type valForA, typename NonDeduced<T>::type valForB)

Upvotes: 2

Ap31
Ap31

Reputation: 3324

Both set(a, b, ValueX, 20, 30) or set<ValueX>(a, 20, b, 30) would require some kind of preprocessing to concatenate "set" and "ValueX" into a new term.

Personally I think a bit of preprocessor is better than a ton of boilerplate, but as @Angew said, you can easily go with the third option:

set(a, b, &PoorLegacyClass::setValueX, 20, 30);

The only thing I wanted to add is you can use variadic templates to make this work for an arbitrary amount of objects:

template<class T> void set(void (PoorLegacyClass::*setter)(T))
{}

template<class T, typename... Args> void set(void (PoorLegacyClass::*setter)(T), 
PoorLegacyClass& obj, T val, Args&&... args)
{
    (obj.*setter)(val);
    set(setter, std::forward<Args>(args)...);
}

The call syntax would slightly change in this case:

set(&PoorLegacyClass::setValueX, a, 20, b, 30);

Upvotes: 1

Nim
Nim

Reputation: 33655

You could try this:

  1. Use BOOST_FUSION_ADAPT_STRUCT to make a fusion sequence of your structure.
  2. Then use the fusion::at_c<N> to access the Nth element and set it's value
  3. Put a free function around the above to take the sequence and use the at_c<N> to access a member appropriately.

Should avoid the need for macros..

Upvotes: 0

Related Questions