Andrewh33
Andrewh33

Reputation: 3

How can I validate the type of a template function argument at compile time

I'm writing a class for communicating sensor data and I want to make it so that a user of the class can't use an incorrect data type for sending sensor data.

I want to set up my class sort of like this:

private:
    static const _sensor_bytes[] = {2, 4, ...};

public:
    enum sensor_name_t {
        SENSOR1 = 0,
        SENSOR2 = 1,
        ...
    };

    template<class T>
    void writeData(sensor_name_t sensor, T data){
        if _sensor_bytes[sensor] != sizeof(T){
            // Do not compile
        }
    }

So for example,

int data = 30;
writeData(SENSOR1, data); // This would not compile because _sensor_bytes[0] = 2 and sizeof(int) = 4
writeData(SENSOR2, data); // This would compile because _sensor_bytes[1] = 4 and sizeof(int) = 4

Am I going about this in the right way? And if so, how would I accomplish this functionality?

Upvotes: 0

Views: 186

Answers (2)

Indranil
Indranil

Reputation: 136

Using size to check the data type may not be the best approach for the following reasons:

  1. Different data types can have the same size, such as float and int.

  2. Using a different datatype does not always imply that the value contained inside the variable is wrong. For example, in your case:

    int data = 30; writeData(SENSOR1, data); // This would not compile because _sensor_bytes[0] = 2 and sizeof(int) = 4

the value 30 can be stored in 2 bytes, so do you really want to throw a compilation error here? However, that is purely implementation specific.

One way you can validate the type of template function argument at compile time is by initializing a dummy variable. Below is an example:

enum sensor_name_t {
    SENSOR1 = 0,
    SENSOR2 = 1
};


int sensor1;
std::string sensor2;


template<class T>
void writeData(sensor_name_t sensor, T data){
    switch(sensor)
    {
    case SENSOR1:
    {
        int temp = data;   // you know that sensor1 is of type int
        // do your stuff
    }
        break;
    case SENSOR2:
    {
        std::string temp = data;   // you know that sensor2 is of type string
        // do your stuff
    }
    break;
    default:
        break;
    }
}

int main()
{
    sensor1 = 30;
    writeData(SENSOR1, sensor1);
    writeData(SENSOR2, sensor1);
}

The above code snippet generates the following compile time error:

error: conversion from 'int' to non-scalar type 'std::string' {aka 'std::__cxx11::basic_string'} requested

The above logic can be implemented in a more elegant way. You can get some idea here

Upvotes: 0

Ihor Drachuk
Ihor Drachuk

Reputation: 1293

You can't do this with function void writeData(sensor_name_t sensor, T data), because value of sensor is known at runtime, so it can't by validated in compile-time and rise an error.

Variant 1

You can try to use std::enable_if, move sensor from function parameter to template parameter and use constexpr for _sensor_bytes. It requires C++11 support by compiler.

#include <cstdint>
#include <type_traits>

class Test {
private:
    static constexpr int _sensor_bytes[] = {2, 4};

public:
    enum sensor_name_t {
        SENSOR1 = 0,
        SENSOR2 = 1
    };

    template<int sensor,
             class T,
             typename = typename std::enable_if<(sizeof(T) == _sensor_bytes[sensor])>::type >
    void writeData(T data) {
        // Use 'sensor' and 'data' here
        // ...
    }
};


int main()
{
    Test test;

    uint16_t data1;
    uint32_t data2;
    test.writeData<Test::SENSOR1>(data1);
    test.writeData<Test::SENSOR2>(data2);
    //test.writeData<Test::SENSOR2>(data1); // Will not compile
    //test.writeData<Test::SENSOR1>(data2); // Will not compile
    return 0;
}

Variant 2

If you still need both function parameters passed in runtime, then validation should be done also in runtime:

  • Use assert
  • throw exception
  • return some error code

Upvotes: 1

Related Questions