Overloading Functions with Dummy Structs vs Template Specialisation

  Kiến thức lập trình

I’m refactoring some C++ and came across some dummy structs being used to differentiate between overloaded functions. I’m considering replacing this with template specialization but want to fully understand the consequences first.

We’ve got an enum class which is often used to decide how to handle some value:

enum class OptionEnum : int {
    optionA,
    optionB
};

void foo( const int val, const OptionEnum option ){
    if( option == OptionEnum::optionA ){
        doSomething( val );
    } else if ( option == OptionEnum::optionB ) {
        doSomethingElse( val );
    } else {
        throw std::invalid_argument( "Unsupported Option" );
    }

    doMoreStuff();
}

However when the function is particularly complex the current code uses overloading and some dummy structs to get the correct overload resolution:

struct OptionsStruct{};
static constexpr struct OptionStructA : OptionsStruct{} optionA;
static constexpr struct OptionStructB : OptionsStruct{} optionB;
static constexpr struct OptionStructC : OptionsStruct{} optionC;

void foo( const int val, const OptionStructA & ){
    doSomething( val );
}

void foo( const int val, const OptionStructB & ){
    doSomethingElse( val );
}

This is often done through some intermediate templated function that doesn’t care which option is used but needs to know which overload to call:

template < typename OptionStructAorB >
void bar( const int val, const OptionStructAorB option ){
    int result = processVal( val );
    foo( result, option );
}

void baz( const int val, const OptionEnum option ){
    if ( option == OptionEnum::optionA ){
        bar( val, optionA );
    } else if ( option == OptionEnum::optionB ) {
        bar( val, optionB );
    } else {
        throw std::invalid_argument( "Unsupported Option in baz" );
    }
}

int main(int argc, char* argv[]) {
    const int inputVal = std::atoi(argv[1]);
    const std::string optionString = argv[2];

    OptionEnum inputOption;
    if ( optionString == "a" ){
        inputOption = OptionEnum::optionA;
    } else if ( optionString == "b" ) {
        inputOption = OptionEnum::optionB;
    } else {
        std::cout << "Invalid option " << optionString << std::endl;
        return 1;
    }

    baz(inputVal, inputOption);

    return 0;
}

I’m considering getting rid of the dummy structs and overloaded functions and replacing them with template specialization like so:

template< OptionEnum option >
void foo(const int val);

template<>
void foo< OptionEnum::optionA >(const int val){
    doSomething(val);
}

template<>
void foo< OptionEnum::optionB >(const int val){
    doSomethingElse(val);
}

template< OptionEnum option >
void bar(const int val){
    int result = processVal(val);
    foo<option>(result);
}

void baz( const int val, const OptionEnum option ){
    if ( option == OptionEnum::optionA ){
        bar<OptionEnum::optionA>( val );
    } else if ( option == OptionEnum::optionB ) {
        bar<OptionEnum::optionB>( val );
    } else {
        throw std::invalid_argument( "Unsupported Option in baz" );
    }
}

Aside from opinionated concerns around style and readability, are there any specific advantages to the original dummy struct approach over the template specialization approach in terms of performance, type safety, and maintainability (specific to C++17)? Could there be maintainability issues when adding a new enum value that is worse in the new approach for example?

(Yes, foo() probably shouldn’t be overloaded at all and we should just have fooA() and fooB() but the codebase is max spaghetti and this is a compromise)

Theme wordpress giá rẻ Theme wordpress giá rẻ Thiết kế website

LEAVE A COMMENT