Adrian Maire
Adrian Maire

Reputation: 14875

Function template specialization to force enum value with type

A function is required to send messages, given a message type and a data structure of a type specific to the message type:

enum class MsgType
{
    msgInt,
    msgDouble,
    msgString
};

template < some template here >
void sendMessage(MsgType type, T value);

I expect to call this function in the following way:

sendMessage( MsgType::msgInt, 42 );
sendMessage( MsgType::msgString, "Answer to the life, etc,etc.");
//sendMessage( MsgType::msgDouble, "Should not compile");
//sendMessage<MsgType::msgString>( "That is easy!" );

How could I implement the previously described template function specialization?

Note: If possible, with C++11, but C++14 is also acceptable.

EDITED:

The current implementation, which only accept the MsgType as template parameter (not as function parameter).

template<MsgType Id, typename T>
void sendMessage(T data);

template<>void sendMesssage<MsgType::msgNumCar, int>( int data ){ //...   }
template<>void sendMesssage<MsgType::msgInt, int>( int data ){ //...   }
template<>void sendMesssage<MsgType::msgString, string>( string data ){ //...   }
// A call
sendMessage<MsgType::msgNumCar>( 42 );

Upvotes: 2

Views: 3695

Answers (4)

Amir Kirsh
Amir Kirsh

Reputation: 13852

Preserving the exact method call you presented cannot give compilation error on the wrong enum type, as the enum type sent is known only at runtime (as a method argument). And you cannot overload on function parameter value (only on type).

Getting the enum as a template parameter (@IdanYadgar) allows check at compile time.

Overloading on type (@MarkB and @RSahu - using two different approaches) is also a nice option.

I believe @RSahu is the closest you can get to what you asked for. Maybe you can add a static const int value for each struct so it will actually be used as enum.

Upvotes: 1

R Sahu
R Sahu

Reputation: 206747

Unless I am totally off base...

You can have three overloads of sendMessage and receiveMessage.

void sendMessage(int m);
void sendMessage(double m);
void sendMessage(std::string const& m);

and

void receiveMessage(int& m);
void receiveMessage(double& m);
void receiveMessage(std::string& m);

There is no need for an enum or a function template.

Update, in response to OP's comment

My suggestion:

  1. Create structs to be used as tags instead of an enum.
  2. Overload sendMessage and receiveMessage using the tag structs.

struct int_message_t {};
struct double_message_t {};
struct string_message_t {};
struct username_message_t {};
struct numcars_message_t {};
struct bigstruct_message_t {};

And then overload them using

void sendMessage(int_message_t, int m);
void sendMessage(double_message_t, double m);
void sendMessage(string_message_t, std::string const& m);
void sendMessage(username_message_t, std::string const& m);
void sendMessage(numcars_message_t, int m);
void sendMessage(bigstruct_message_t, big_struct const& m);

and

void receiveMessage(int_message_t, int& m);
void receiveMessage(double_message_t, double& m);
void receiveMessage(string_message_t, std::string& m);
void receiveMessage(username_message_t, std::string& m);
void receiveMessage(numcars_message_t, int& m);
void receiveMessage(bigstruct_message_t, big_struct& m);

If you can use a function template to make the implementations of these functions, that will be nice but I would still leave that as an implementation detail.

Upvotes: 2

Idan Yadgar
Idan Yadgar

Reputation: 1034

Templates can have constant parameters which are known in compile-time. You could do the following:

enum MsgType {
    msgInt,
    msgDouble,
    msgString
};

template<int>
struct Message { };

template<>
struct Message<MsgType::msgInt> {
    static void send(int value) { cout << "int: " << value; }
};

template<>
struct Message<MsgType::msgDouble> {
    static void send(double value) { cout << "double: " << value; }
};

template<>
struct Message<MsgType::msgString> {
    static void send(const char* value) { cout << "string: " << value; }
};

And invoke it:

Message<MsgType::msgInt>::send(5);
Message<MsgType::msgDouble>::send(3.14);
Message<MsgType::msgString>::send("hello");

// will not compile
//Message<MsgType::msgDouble>::send("should not compile");

notice that the template parameter must be a constant (i.e. known at compile time). Which means that the following code won't compile:

int type = MsgType::msgInt;
Message<type>::send(123);

But, why won't you just create 3 overloads for sendMessage?

Upvotes: 4

Mark B
Mark B

Reputation: 96311

Instead of validating that the user passes the correct message type enum value into the send function, prevent it entirely with some traits:

#include <iostream>

enum class MsgType
{
    msgInt,
    msgDouble,
    msgString
};

template <typename T>
struct MsgTypeOf;

template <>
struct MsgTypeOf<int>
{
    static MsgType msg_type;
};

template <>
struct MsgTypeOf<double>
{
    static MsgType msg_type;
};

template <>
struct MsgTypeOf<const char*>
{
    static MsgType msg_type;
};

MsgType MsgTypeOf<int>::msg_type = MsgType::msgInt;
MsgType MsgTypeOf<double>::msg_type = MsgType::msgDouble;
MsgType MsgTypeOf<const char*>::msg_type = MsgType::msgString;

template <typename T>
int sendMessage(T value)
{
    return static_cast<int>(MsgTypeOf<T>::msg_type);
}

int main()
{
    std::cout << sendMessage(42) << std::endl;
    std::cout << sendMessage("Answer to the life, etc,etc.") << std::endl;
    std::cout << sendMessage(42.42) << std::endl;
}

Output: 0 2 1

Upvotes: 3

Related Questions