Reputation: 169
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
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:
// ...
};
Upvotes: 0
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