Enzo Ferrazzano
Enzo Ferrazzano

Reputation: 169

Improve compile-time error messages in templated classes

I have a templated class which is supposed to accepts some kind of containers (std::array and std::vector) of some kind of of objects (in this example string and doubles).

My goal is to provide some clear compilation error if the class is constructed from the wrong combination of objects. The following code compiles in Visual Studio 17, version 15.9.0.

///Performing checks 
namespace checks
{
    template <typename T>
    struct CorrectType {
        enum { value = false };
    };

    template <>
    struct CorrectType<std::string> {
        enum { value = true };
    };
    template <>
    struct CorrectType<double> {
        enum { value = true };
    };

    template <typename T>
    struct CorrectContainer {
        enum { value = false };
    };

    template <typename T, typename A>
    struct CorrectContainer<std::vector<T, A>> {
        enum { value = CorrectType<T>::value };
    };

    template <typename T, std::size_t N>
    struct CorrectContainer<std::array<T, N>> {
        enum { value = CorrectType<T>::value };
    };
    template <class Container>
    void constexpr check(){
        static_assert(checks::CorrectContainer<Container>::value, "Wrong container: only vectors/arrays of doubles/strings are accepted");
    }
}


template <typename Container>
class Wrapper
{
public:

    explicit Wrapper(const Container &container) :  container_(container), size_(container.size())
    {
  //type checking is performed
        checks::check<Container>();
    }

    void display() const {
        for (int i = 0; i < size_; i++)
            std::cout << this->container_[i] << " ";
        std::cout << std::endl;
    }

private:
    Container container_;
    int size_ = 0;
};

int main()
{
    //Ok
    Wrapper array_wrapper(std::array<double, 5>{0.0,1.0,2.0,3.0,4.0});
    array_wrapper.display();
    //Ok
    Wrapper string_wrapper(std::array<std::string, 3>{ "a","b","c" });
    string_wrapper.display();

    //Error - working as intended but not clear what went wrong
    Wrapper<std::vector<int>> vector_wrapper({ 1,2,3});
    vector_wrapper.display();
}

The code above works as intended, but the error is ambiguous: we are not able to understand if the container is wrong or the kind of object contained is. Moreover, if the templated object has not a size member function, it will fail too early.

Upvotes: 0

Views: 202

Answers (2)

Jarod42
Jarod42

Reputation: 218323

You might move your check inside class (and split your condition):

template <typename Container>
class Wrapper
{
    static_assert(checks::CorrectContainer<Container>::value,
                  "only std::vector/std::array allowed");
    static_assert(checks::CorrectType<typename Container::value_type>::value,
                  "only double and std::string");

public:
    // ...
};

Demo

Upvotes: 0

bipll
bipll

Reputation: 11950

As I understand your query, invalid type detection can probably be sketched out as

template<class> struct ValidType;

template<template<class...> class> struct ValidContainer: std::false_type {};
template<> struct ValidContainer<std::vector>: std::true_type {};

template<class> struct ValidType: std::false_type {};
template<class> struct ValidType<double>: std::true_type {};
template<class> struct ValidType<std::string>: std::true_type {};

template<class> struct ValidArgument {
    static_assert(false, "Both container and element type are wrong");
};  

template<template<class...> class Ctr, class T, class... Ts>
struct ValidArgument<Ctr<T, Ts...>> {
    static_assert(ValidContainer<Ctr>::value || ValidType<T>::value
            , "Both container and element type are wrong");
    static_assert(ValidContainer<Ctr>::value, "Container type is wrong");
    static_assert(ValidType<T>::value, "Element type is wrong");
};

template<class T, std::size_t n> struct ValidArgument<std::array<T, n>> {
    static_assert(ValidType<T>::value, "Element type is wrong");
};  

(Note this is not the real code, merely a demonstration of an idea.)

Arrays are still evil, in the sense that std::array has a non-type parameter, and thus you cannot have a single template that checks the container, the ultimate check is still for the kind 0-type, with container checking in generic case and std::array being treated separately.

Alternatively, ValidType can be slightly more compact:

template<class T> using ValidType =             std::bool_constant<
        std::is_same_v<T, double> || std::is_same_v<T, std::string>>;

Or

template<class T> using ValidType = std::disjunction<
        std::is_same<T, double>, std::is_same<T, std::string>>;

Or a less-standard type matching class. For instance:

template<class T, class... Ts> inline constexpr bool is_one_of_v =
        std::disjunction_v<std::is_same<T, Ts>...>;
template<class T> using ValidType =
        std::bool_constant<is_one_of_v<T, double, std::string>>;

This way you're less likely to get rogue specializations later.

Upvotes: 1

Related Questions