Change the arguments of a function depending on template?

I'm very new to C++ so I'm curious if this is possible. I'm building a little module to control some lights of mine. I have an enum called "ControllerTypes". It holds three types "CONTROLLER_FASTLED", "CONTROLLER_ADAFRUIT_NEOPIXEL", "CONTROLLER_CALLBACK".

I have a class "CMagicLight" which has a static member "setup" with a template, like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef enum {
    CONTROLLER_FASTLED,
    CONTROLLER_ADAFRUIT_NEOPIXEL,

    CONTROLLER_CALLBACK,
} ControllerType;


class CMagicLight
{

    public:

        CMagicLight();

        static CLightController &setup();

        
        template<ControllerType cT> CLightController &setup() {
            return setup();
        }
};


Currently setup has no arguments but that is what my question is. Depending on what the user choses the controller type to be is it possible to get the arguments of setup to change? For example:
If the user selects: CONTROLLER_FASTLED, "setup" should accept either:
- pin
- numLeds
- type
- leds
OR
- (arguments of type) CLEDController
If the user selects: CONTROLLER_ADAFRUIT_NEOPIXEL, "setup" should accept either:
- pin
- numLeds
- type
OR
- (arguments of type) Adafruit_Neopixel
and so on for the callback and others in the future.

Thanks, Dream.
Last edited on
We can implement it as a variadic template and then use tag dispatching to select the right overload of set_up().

Here is a skeleton of the idea; this may not be easy for you to digest if you are very new to C++.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
enum ControllerType {

    CONTROLLER_FASTLED,
    CONTROLLER_ADAFRUIT_NEOPIXEL,
    CONTROLLER_CALLBACK,
};

// convert the value of the enum to an empty tag type: it has nothing in it
// however, for each ControllerType X, the instantiated type enum_to_type_t<X> is unique
// and we can use it to select the appropriate (the correct) overload of a function
// (we need to do this because overload resolution is based on types, not values)
template < ControllerType CT > struct enum_to_type_t {} ;
template < ControllerType CT > const enum_to_type_t<CT> enum_to_type_v ; // and a helper variable template

struct CLEDController ;
struct Adafruit_Neopixel ;

// a set of overloads to set up various controller types
// all these do_set_up functions take a tag as the first argument

// set up CONTROLLER_FASTLED
void do_set_up( enum_to_type_t<CONTROLLER_FASTLED>, int pin /* other args for set up of CONTROLLER_FASTLED */ ) { /* ... */ }
void do_set_up( enum_to_type_t<CONTROLLER_FASTLED>, const CLEDController& clc ) { /* ... */ }

// set up CONTROLLER_ADAFRUIT_NEOPIXEL
void do_set_up( enum_to_type_t<CONTROLLER_ADAFRUIT_NEOPIXEL>, int pin /* other args for set up of CONTROLLER_ADAFRUIT_NEOPIXEL */ ) { /* ... */ }
void do_set_up( enum_to_type_t<CONTROLLER_ADAFRUIT_NEOPIXEL>, const Adafruit_Neopixel& afnp ) { /* ... */ }

// etc.

// this is a variadic template; it can accept multiple arguments of different types
template < ControllerType CT, typename... SETUP_ARGS >
void set_up( SETUP_ARGS&&... args )
{
    // tag dispatch: the right overload is selected based on the tag enum_to_type_v<CT> (the first argument)
    // we forward the other arguments to the chosen overload of do_set_up
    do_set_up( enum_to_type_v<CT>, std::forward<SETUP_ARGS>(args)... ) ;
}
Thanks for the reply!

The more I read it the more I am getting it. I missed in my research the ability to accept multiple arguments of different types with the variadic templates. I was under the impression that that wasn't actually possible in c++ but I guess I was wrong.

Thanks again - it satisfies my want for things to be, for lack of a better word, "modular" even if that means sacrificing the neatness of the code (basically making everything much more complicated than it needs to be). It would be fairly to easy to just make them into constructor parameters but that's boring!
I have to admit I didn't actually try it because I was finishing my rough implementation just to get an idea of how the processing would work. I've tried it out now and I have 1 issue and 1 question.
The issue I am getting is it is saying: "enum_to_type_v" is not a function or static data member

My question is how would I define the "setup" / "do_set_up" functions in the cpp file? I've heard it's not recommended to use templates in the cpp file but because of the nature of the arguments a template would be necessary right?

Thanks for the help so far!
> "enum_to_type_v" is not a function or static data member

variable templates were introduced in C++14. https://en.cppreference.com/w/cpp/language/variable_template
We can live without it (we can instantiate anonymous tag objects).


> how would I define the "setup" / "do_set_up" functions in the cpp file?

The do_set_up functions are not templates; they need to be declared in the header file,
and can be implemented (defined) in the .cpp file(s)

set_up is a template; we would define it in the header file.


Something like this (version without the variable template):

header file .h:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <utility> // std::forward

enum ControllerType {

    CONTROLLER_FASTLED,
    CONTROLLER_ADAFRUIT_NEOPIXEL,
    CONTROLLER_CALLBACK
};

// convert the value of the enum to an empty tag type: it has nothing in it
// however, for each ControllerType X, the instantiated type enum_to_type_t<X> is unique
// and we can use it to select the appropriate (the correct) overload of a function
// (we need to do this because overload resolution is based on types, not values)
template < ControllerType CT > struct enum_to_type_t {} ;

// a set of overloads to set up various controller types
// all these do_set_up functions take a tag as the first argument
// these are declared in the header file

// types used in the declarations are also declared in the header
struct CLEDController ;
struct Adafruit_Neopixel ;
// ...

// set up CONTROLLER_FASTLED
void do_set_up( enum_to_type_t<CONTROLLER_FASTLED>, int pin /* other args for set up of CONTROLLER_FASTLED */ ) ;
void do_set_up( enum_to_type_t<CONTROLLER_FASTLED>, const CLEDController& clc ) ;

// set up CONTROLLER_ADAFRUIT_NEOPIXEL
void do_set_up( enum_to_type_t<CONTROLLER_ADAFRUIT_NEOPIXEL>, int pin /* other args for set up of CONTROLLER_ADAFRUIT_NEOPIXEL */ ) ;
void do_set_up( enum_to_type_t<CONTROLLER_ADAFRUIT_NEOPIXEL>, const Adafruit_Neopixel& afnp ) ;

// etc.

// this is a variadic te0mplate; it can accept multiple arguments of different types
// template, defined in the header file
template < ControllerType CT, typename... SETUP_ARGS >
void set_up( SETUP_ARGS&&... args )
{
    // tag dispatch: the right overload is selected based on the tag enum_to_type_v<CT> (the first argument)
    // we forward the other arguments to the chosen overload of do_set_up
    // not using a variable template, so instantiate an anonymous tag object with enum_to_type_t<CT>{}
    do_set_up( enum_to_type_t<CT>{}, std::forward<SETUP_ARGS>(args)... ) ;
}


implementation file(s) .cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// overloads of do_set_up to set up various controller types
// these are defined in the implementation file(s)

#include <iostream>

// include the headers for types required for the definitions
#include "CLEDController.h"
#include "Adafruit_Neopixel.h"
// ...

// set up CONTROLLER_FASTLED
void do_set_up( enum_to_type_t<CONTROLLER_FASTLED>, int pin /* other args for set up of CONTROLLER_FASTLED */ )
{
    std::cout << "setting up CONTROLLER_FASTLED: pin == " << pin /* << ... */ << '\n' ;
    // ...
}

void do_set_up( enum_to_type_t<CONTROLLER_FASTLED>, const CLEDController& clc )
{
    std::cout << "setting up CONTROLLER_FASTLED: CLEDController == " /* << ... */ << '\n' ;
    // ...
}


// set up CONTROLLER_ADAFRUIT_NEOPIXEL
void do_set_up( enum_to_type_t<CONTROLLER_ADAFRUIT_NEOPIXEL>, int pin /* other args for set up of CONTROLLER_ADAFRUIT_NEOPIXEL */ )
{
    std::cout << "setting up CONTROLLER_ADAFRUIT_NEOPIXEL: pin == " << pin /* << ... */ << '\n' ;
    // ...
}

void do_set_up( enum_to_type_t<CONTROLLER_ADAFRUIT_NEOPIXEL>, const Adafruit_Neopixel& afnp )
{
    std::cout << "setting up CONTROLLER_ADAFRUIT_NEOPIXEL: Adafruit_Neopixel == " /* << ... */ << '\n' ;
    // ...
}

// etc. 


That's fixed it! Thank you so much for the help I really appreciate it!
Topic archived. No new replies allowed.