Template excluding one type - c++

I want to make a parse function that excludes the type string or char
template <typename T>
bool parse(T & value, const string & token){
istringstream sin(token);
T t;
if( !(sin >>t) ) return false;
char junk;
if( sin >>junk ) return false;
value = t;
return true;
}
How can I do this?

Depending on what you mean by exclude the types string or char. If you want it not to link you can declare but not define specializations for the types:
template <>
void parse<std::string>( std::string & value, const std::string& token );
The compiler will see the specialization and not generate the code. The linker will fail as the symbol is not defined in any translation unit.
The second approach, a bit more complicated is not to fail at link time, but to make the compiler not accept the template for those types. This can be done with SFINAE, which is simpler in C++11, but if you need a C++03 solution you can google for it or add a comment:
template <typename T,
typename = typename std::enable_if<!std::is_same<T,std::string>
&& !std::is_same<T,char>>::type >
void parse( T & t, const std::string& token ) {
// ...
}
(I have not run this through a compiler so the syntax might be a bit off, play with it) The compiler will see the template, and when it tries to perform the substitution of the type T it will fail due to the std::enable_if<...>::type not resolving to a type.
In general, what you probably want is to provide different overloads that perform a specific version of the parse and take precedence:
void parse( std::string& v, const std::string& token ) {
v = token;
}
Note that this is not a template, but a regular function. A non-templated function will be a better match than a templated one when the arguments of the call are perfect matches.

You can use boost type traits. They have a type comparison check called is_same
http://www.boost.org/doc/libs/1_51_0/libs/type_traits/doc/html/boost_typetraits/reference/is_same.html
You can just use the result of the comparison to char and string.

template should be declared like this in
David Rodríguez's method (for functions with return type):
template <template T, typename std::enable_if<!std::is_same<T,std::string>::value>::type* = nullptr >
T func(T x){}
For multiple condition:
template <template T, typename std::enable_if<!std::is_same<T,std::string>::value &&!std::is_same<T,int>::value>::type* = nullptr >
T func(T x){}
For functions without return type:
template <template T>
typename std::enable_if<!std::is_same<T,std::string>::value>::type
func(T x){}
For multiple condition:
template <template T>
typename std::enable_if<!std::is_same<T,std::string>::value &&!std::is_same<T,int>::value>::type
func(T x){}
Don't forget to include #include <type_traits>

Related

C++ template functions accepting parameters in random order

I am writing a C++ network library and would like the main (template) function to accept parameters in random order, to make it more user friendly, in the same way the CPR library does.
The template function will accept up to 10 parameters at the same time, each a different type. Is there a way to instantiate the template to accept any random order of param types, other than having to manually include code for every possibility?
For example - in this case using 3 params each a different type:
.h file
namespace foo
{
template <typename T, typename U, typename V> void do(const T& param_a, const U& param_b , const V& param_c);
};
.cpp file
template <typename T, typename U, typename V>
void foo::do(const T& param_a, const U& param_b, const V& param_c) {
//do lots of stuff
}
//instantiate to allow random param order
template void foo::do<int, std::string, long>(const int&, const std::string&, const long&);
template void foo::do<int, long, std::string>(const int&, const long&, const std::string&);
template void foo::do<int, std::string, int>(const int&, const std::string&, const int&);
//etc... to cover all possible param orders
If your goal is to match the API design of a given library, best way to learn is to dig into its source code and dissect it.
Consider this snippet of code (I'm still using CPR as an example as you mentionned it as a reference):
cpr::Session session;
session.SetOption(option1);
session.SetOption(option2);
session.SetOption(option3);
You want a method which can handle option1, option2, ... , no matter in which order they are provided. The subsequent calls to SetOption could be replaced with a single SetOptions(option3, option1, option2). Therefore we need a variadic SetOptions method:
template<typename Ts...> // important: don't specialize the possible argument types here
void SetOptions(Ts&&... ts)
{ /* do something for each param in ts... */ }
The question is "how do you call SetOption for each item inside the ts parameter-pack ?". This is a mission for std::initializer_list. You can find a simple example here.
The key here is to have an overloaded function which can handle each argument type separately (example in CPR with SetOptions). Then, inside your "permutable" function, you call the overloaded function for each of your arguments, one at a time (example in CPR, which is then used in various places).
One thing to note though is that you can pass multiple parameters of the same type. Depending on what you want to achieve, this can be an issue or not.
Also, you can call the method with unsupported argument types (matching none of your overloads), in this case the error message is not always explicit depending on which compiler you are using. This is — however — something you could overcome using static_asserts.
Is there a way to instantiate the template to accept any random order of param types, other than having to manually include code for every possibility?
You cannot do this for explicit instantiation definitions without macros, but you could use a separate approach and rely on implicit instantiations instead, using SFINAE to restrict the primary template (whose definition you move to the header file) based on two custom traits.
To begin with, given the following type sequence
template <class... Ts>
struct seq {};
we want to construct a trait that, for a given type sequence seq<T1, T2, ...> (your "10 parameter types"), denoted as s:
s shall be a subset of set of types of your choosing seq<AllowedType1, ...>, and
s shall contain only unique types.
We can implement the former as:
#include <type_traits>
template <class T, typename... Others>
constexpr bool is_same_as_any_v{(std::is_same_v<T, Others> || ...)};
template <typename, typename> struct is_subset_of;
template <typename... Ts, typename... Us>
struct is_subset_of<seq<Ts...>, seq<Us...>> {
static constexpr bool value{(is_same_as_any_v<Ts, Us...> && ...)};
};
template <typename T, typename U>
constexpr bool is_subset_of_v{is_subset_of<T, U>::value};
and the latter as
template <typename...> struct args_are_unique;
template <typename T> struct args_are_unique<T> {
static constexpr bool value{true};
};
template <typename T, typename... Ts> struct args_are_unique<seq<T, Ts...>> {
static constexpr bool value{!is_same_as_any_v<T, Ts...> &&
args_are_unique<seq<Ts...>>::value};
};
template <typename... Ts>
constexpr bool args_are_unique_v{args_are_unique<Ts...>::value};
after which we can define the primary template as
namespace foo {
namespace detail {
using MyAllowedTypeSeq = seq<int, long, std::string>; // ...
} // namespace detail
template <
typename T, typename U, typename V, typename Seq = seq<T, U, V>,
typename = std::enable_if_t<is_subset_of_v<Seq, detail::MyAllowedTypeSeq> &&
args_are_unique_v<Seq>>>
void doStuff(const T &param_a, const U &param_b, const V &param_c) {
// do lots of stuff
}
} // namespace foo
and where we may and may not use the primary template overload as follows:
int main() {
std::string s{"foo"};
int i{42};
long l{84};
foo::doStuff(s, i, l); // OK
foo::doStuff(s, l, i); // OK
foo::doStuff(l, i, s); // OK
foo::doStuff(l, s, i); // OK
// uniqueness
foo::doStuff(l, l, i); // Error: candidate template ignored
// wrong type
unsigned int ui{13};
foo::doStuff(s, ui, l); // Error: candidate template ignored
}
If types need not actually be unique (it's somewhat unclear from the question) you can simply SFINAE-constrain the primary template only on the first is_subset_of_v trait:
template <
typename T, typename U, typename V, typename Seq = seq<T, U, V>,
typename = std::enable_if_t<is_subset_of_v<Seq, detail::MyAllowedTypeSeq>>>
void do(const T &param_a, const U &param_b, const V &param_c) {
// do lots of stuff
}
Why not use the builder pattern here? You would create a foo_builder with various setXxx methods and a final build() to get the fully configured object.
Use a struct to hold all the params.
namespace foo
{
struct do_params {
int a;
long b;
std::string c;
};
void do(do_params params);
};

How to decltype template method C++?

I write interfaces through concepts for implementation validation.
There are no problems with conventional methods:
// Interface realization
struct Realization
{
int* TestMethod(const std::string& aStr)
{
return (int *) aStr.c_str();
}
};
// Concept
template <typename T>
concept IRealization = std::is_same_v<decltype(&T::TestMethod), int* (T::*)(const std::string&)>;
// and then, for example
void Check()
{
static_assert(IRealization<Realization>)
}
but when I try to write a similar check for a template method:
// Interface realization
struct Realization
{
template <typename T>
int* TemplateMethod(const T& aStr)
{
return (int *) aStr.c_str();
}
};
, I run into a problem of dectype a template method, because I cant write
decltype(&RealizationImpl::TemplateMethod)
(at the time of checking the interface, I do not know the type that will be substituted)
Please tell me, can I somehow get the signature of the template function without type, or otherwise solve my problem? Thanks!
You should not write concepts like this. A concept should never check for something as specific as a member function with an exact signature. A concept should instead say that, given an instance of the type in question, I should be able to do i.memberFunc(...), where ... is the list of parameters.
For example, your "IRealization" concept (please don't prefix concepts with I. Concepts are not interfaces) ought to say "T must have a member function which can be called given a std::string argument and results in something which is convertible to an int." That would look like:
template <typename T>
concept IRealization = requires(T t, std::string str)
{
{ t.TestMethod(str) } -> convertible_to<int>;
};
This allows the user to provide a TestMethod that takes, for example, std::string_view instead of std::string. There's no point in being so incredibly restrictive on the type.
A concept that checks for T having a member function which is callable with some type U would have to be templated on both T and U:
template <typename T, typename U>
concept IRealization = requires(T t, U u)
{
{ t.TestMethod(u) } -> convertible_to<int>;
};
What is the problem with adding another type to the concept?
// Concept
template <typename T, typename U>
concept IRealization = std::is_same_v<decltype(&T::template TestMethod<U>), int* (T::*)(const U&)>;
For instance.
You could even make it prettier by creating a typedef -
template<typename T, typename U>
using FuncT = decltype(&T::template TestMethod<U>);

What are the syntax and semantics of C++ templated code?

template<typename T, size_t M, size_t K, size_t N, typename std::enable_if_t<std::is_floating_point<T>::value, T> = 0>
void fastor2d(){//...}
I copied this line of code from cpp-reference(only the std::enable_if part, i do need T and all three of the size_t's), because i would like to use this function only when floating_types are used on it ... it does not compile.
Could somebody explain to me, why, and what it even does? While i am at it, how do you call this function afterwards?
Every tutorial or question here on SO gets bombed with answers, and that is great, but to someone who does not understand jacks*** of what is happening, even those are not really helpful.(sry, if possibly slightly agitated or aggressive)
EDIT: i greatly appreciate all answers as of now, i realize that my wording might have been a bit off ... i understand what a template parameter is, and know the difference between runtime and compiletime etc, but i just cant get a good grasp of the syntax behind std::enable_if
EDIT2:
template<typename T, size_t M, size_t K, size_t N, typename = std::enable_if_t<std::is_integral<T>::value>>
void fastor2d(){
Fastor::Tensor<T,M,K> A; A.randInt();
}
This is literally the only thing i need changed. Notice the random() part
template<typename T, size_t M, size_t K, size_t N, typename = std::enable_if_t<std::is_floating_point<T>::value>>
void fastor2d(){
Fastor::Tensor<T,M,K> A; A.random();
}
I'll try to explain this as simple as possible not to go into the language details too much since you asked for it.
Template arguments are compile time arguments (they do not change during the run-time of your application). Function arguments are run-time and have a memory address.
Calling this function would look something like this:
fastor2d<Object, 1, 2, 3>();
In the <> brackets you see the compile-time arguments or more accurately the template parameters, and the function in this case takes 0 runtime arguments in the () brackets. The last compile time argument has a default argument which is used to check whether the function should compile at all (enable_if type). If you want to know more clearly what enable if does you should search for the term SFINAE, which is a template metaprogramming technique used to determine whether a function or class should exist or not.
Here is a short SFINAE example:
template<typename T, typename = std::enable_if_t<std::is_floating_point<T>::value>>
void function(T arg)
{
}
function(0.3f); //OK
function(0.0); //OK double results in std::is_floating_point<double>::value == true
function("Hello"); //Does not exist (T is not floating point)
The reason the third function call fails, is because the function does not exist. This is because the enable if caused the function not to exist when the compile-time bool that is passed in as its' template argument is false.
std::is_floating_point<std::string>::value == false
Do note that a lot of people agree that the SFINAE syntax is horrible and that a lot of SFINAE code will not be necessary anymore with the introduction of concepts and constraints in C++ 20.
Rather than a top-down approach starting with you code snippet, I'll take a bottom-up approach to explain some important details about templates and what tools and techniques are involved.
At heart, templates are a tool that let you write C++ code that applies to a range of possible types, not strictly for a fixed type. In a statically-typed language, this is firstly a great tool for reusing code without sacrificing type safety, but in C++ in particular, templates are very powerful because they can be specialized.
Every template declaration begins with the keyword template, and a list of type or non-type (i.e value) parameters. Type parameters use the special keyword typename or class, and are used to let your code work over a range of types. Non-type parameters simply use the name of an existing type, and these let you apply your code to a range of values that are known at compile-time.
A very basic templated function might look like the following:
template<typename T> // declare a template accepting a single type T
void print(T t){ // print accepts a T and returns void
std::cout << t; // we can't know what this means until the point where T is known
}
This lets us reuse code safely for a range of possible types, and we can use it as follows:
int i = 3;
double d = 3.14159;
std::string s = "Hello, world!";
print<int>(i);
print<double>(d);
print<std::string>(s);
The compiler is even smart enough to deduce the template parameter T for each of these, so you can safely get away with the following, functionally identical code:
print(i);
print(d);
print(s);
But suppose you want print to behave differently for one type. Suppose, for example, you have a custom Point2D class that needs special handling. You can do this with a template specialization:
template<> // this begins a (full) template specialization
void print<Point2D>(Point2D p){ // we are specializing the existing template print with T=Point2D
std::cout << '(' << p.x << ',' << p.y << ')';
}
Now, anytime we use print with T=Point2D, the specialization is chosen. This is really useful, for example, if the generic template just doesn't make sense for one specific type.
std::string s = "hello";
Point2D p {0.5, 2.7};
print(s); // > hello
print(p); // > (0.5,2.7)
But what if we want to specialize a template for many types at once, based on a simple condition? This is where things become a little meta. First, let's try to express a condition in a way that lets them be used inside templates. This can be a little tricky because we need compile-time answers.
The condition here will be that T is a floating point number, which is true if T=float or T=double and false otherwise. This is actually fairly simple to achieve with template specialization alone.
// the default implementation of is_floating_point<T> has a static member that is always false
template<typename T>
struct is_floating_point {
static constexpr bool value = false;
};
// the specialization is_floating_point<float> has a static member that is always true
template<>
struct is_floating_point<float> {
static constexpr bool value = true;
};
// the specialization is_floating_point<double> has a static member that is always true
template<>
struct is_floating_point<double> {
static constexpr bool value = true;
}
Now, we can query any type to see if it's a floating point number:
is_floating_point<std::string>::value == false;
is_floating_point<int>::value == false;
is_floating_point<float>::value == true;
is_floating_point<double>::value == true;
But how can we use this compile-time condition inside another template? How can we tell the compiler which template to choose when there are many possible template specializations to choose from?
This is achieved by taking advantage of a C++ rule called SFINAE, which in basic English, says, "when there are many possible template specializations to choose from, and the current one doesn't make sense*, just skip it and try the next one."
There's a list of errors, when attempting to substitute template arguments into templated code, that cause the template to be ignored without an immediate compiler error. The list is a bit long and complex.
One possible way that a template doesn't make sense is if it tries to use a type that doesn't exist.
template<typename T>
void foo(typename T::nested_type x); // SFINAE error if T does not contain nested_type
This is the exact same trick that std::enable_if uses under the hood. enable_if is a template class accepting a type T and a bool condition, and it contains a nested type type equal to T only when the condition is true. This is also pretty easy to achieve:
template<bool condition, typename T>
struct enable_if {
// no nested type!
};
template<typename T> // partial specialization for condition=true but any T
struct enable_if<true, T> {
typedef T type; // only exists when condition=true
};
Now we have a helper that we can use in place of any type. If the condition we pass is true, then we can safely use the nested type. If the condition we pass is false, then the template is no longer considered.
template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, void>::type // This is the return type!
numberFunction(T t){
std::cout << "T is a floating point";
}
template<typename T>
typename std::enable_if<!std::is_floating_point<T>::value, void>::type
numberFunction(T t){
std::cout << "T is not a floating point";
}
I completely agree that std::enable_if<std::is_floating_point<T>::value, void>::type is a messy way to spell out a type. You can read it as "void if T is floating point, and otherwise stop and try the next overload"
Finally, to take apart your example:
// we are declaring a template
template<
typename T, // that accepts some type T,
size_t M, // a size_t M,
size_t K, // a size_t K,
size_t N, // a size_t N,
// and an unnamed non-type that only makes sense when T is a floating point
typename std::enable_if_t<std::is_floating_point<T>::value, T> = 0
>
void fastor2d(){//...}
Note the = 0 at the end. That's simply a default value for the final template parameter, and it lets you get away with specifying T, M, K, and N but not the fifth parameter. The enable_if used here means that you can provide other templates called fastor2d, with their own sets of conditions.
First of all, I'll rewrite your function in a working form
template <typename T, size_t M, size_t K, size_t N,
std::enable_if_t<std::is_floating_point<T>::value, int> = 0>
void fastor2d() // ..........................................^^^ int, not T
{ }
The point is that I've changed the second template argument of std::enable_if_t form T to int.
I've also removed the typename before std::enable_if_t but isn't important: the typename is implicit in the _t at the end of std::enable_if_t, introduced from C++14. In C++11 the correct form is
// C++11 version
typename std::enable_if<std::is_floating_point<T>::value, int>::type = 0
// ^^^^^^^^ no _t ^^^^^^
But why it works?
Start from the name: SFINAE.
Is a short form for "Substitution Failure Is Not An Error".
It's a C++ rule so that when you write some thing as
template <int I, std::enable_if_t< I == 3, int> = 0>
void foo ()
{ }
and I is 3, the condition of std::enable_if_t is true so std::enable_if_t< I == 3, int> is substituted with int so foo() is enabled but when I isn't 3, the condition of std::enable_if_t if false so std::enable_if_t< I == 3, int> is not substituted so foo() isn't enabled but this ins't an error (if, through overloading, there is another foo() function, enabled, that matches the call, obviously).
So where is the problem in your code?
The problem is that std::enable_if_t is substituted, when the first template parameter is true, with the second parameter.
So if you write
std::enable_if_t<std::is_floating_point<T>::value, T> = 0
and you call
fastor2d<float, 0u, 1u, 2u>();
the std::is_floating_point<float>::value (but you can also use the shorter form std::is_floating_point_v<T> (_v and not ::value)) so the substitution take place and you get
float = 0
but, unfortunately, a template value (not type) parameter can't be of type floating point, so you get an error.
If you use int instead of T, the substitution give you
int = 0
and this is correct.
Another solution can be use the following form
typename = std::enable_if_t<std::is_floating_point<T>::value, T>
as suggested by Andreas Loanjoe, because the substitution give you
typename = float
that is a valid syntax.
But this solution has the drawback that doesn't works when you want to write two alternative functions, as in the following example
// the following solution doesn't works
template <typename T,
typename = std::enable_if_t<true == std::is_floating_point<T>::value, int>>
void foo ()
{ }
template <typename T,
typename = std::enable_if_t<false == std::is_floating_point<T>::value, int>>
void foo ()
{ }
where works the solution based on the value
// the following works
template <typename T,
std::enable_if_t<true == std::is_floating_point<T>::value, int> = 0>
void foo ()
{ }
template <typename T,
std::enable_if_t<false == std::is_floating_point<T>::value, int> = 0>
void foo ()
{ }

Prevent instantiation of template class for types not supported by stringstream extraction operator (>>)

I'm trying to learn a bit about templates and metafunctions, namely std::enable_if. I'm making a menu system for our school assignments (extracurricular, mind you), and need a way of getting input from the user. I'd like to define a template class for various types of input - something used along the lines of:
std::string userInput = Input<std::string>("What's your name?").Show();
float userHeight = Input<float>("How tall are you?").Show();
I'd like to (and I'm sure there are reasons not to, but nevertheless) do this generalized sort of conversion using a std::stringstream: get input from user, feed into SS, extract into variable of type T.
It's easy enough to see if the conversion failed during runtime, but I'd like to use std::enable_if to prevent people from using my Input<> class for cases where conversion is impossible, say:
std::vector<Boats> = Input<std::vector<>>("Example").Show();
Obviously a std::stringstream cannot convert a string to a vector, so it will always fail.
My question is this:
Can I format an std::enable_if clause to ONLY allow instantiation of my template class for the types listed above? Alternatively, is there a better way to go about it? Have I got things completely the wrong way around?
What I've done so far
I believe I have found a list of allowed types that std::stringstream can "convert" a string into:
http://www.cplusplus.com/reference/istream/istream/operator%3E%3E/
I've been using std::enable_if like this up until this point:
template <typename T, typename = typename
std::enable_if<std::is_arithmetic<T>::value, T>::type>
However, now I'd like to extend it to allow not only arithmetic values, but all values supported by the sstream >> operator.
If you prefer to use a SFINAE with a class template parameter, then you want
template <
typename T,
typename = decltype(std::declval<std::istringstream &>() >> std::declval<T &>(), void())
>
class Input /*...*/
I think that you are trying to use std::enable_if for something which doesn't require it. If your template function already relies on operator<< applied on a generic type T, then compilation will fail in any case if the operator is not specialized for that type.
Nothing prevents you from using std::enable_if to solve your specific problem, though that may be not the best way to do it.
If C++20 concepts were already largely adopted I'd say that that would be your way to go.
You could go the way as suggested here on SO and implement a class is_streamable which can check for that like this:
#include <type_traits>
#include <utility>
#include <iostream>
#include <sstream>
template<typename S, typename T>
class is_streamable
{
template<typename SS, typename TT>
static auto test(int)
-> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type());
template<typename, typename>
static auto test(...)->std::false_type;
public:
static const bool value = decltype(test<S, T>(0))::value;
};
class C
{
public:
friend std::stringstream& operator<<(std::stringstream &out, const C& c);
};
std::stringstream& operator<<(std::stringstream& out, const C& c)
{
return out;
}
int main() {
std::cout << is_streamable<std::stringstream, C>::value << std::endl;
return 0;
}
This would return one if the operator is implemented and zero if not.
With that you can alter your snippet to
template <typename T, typename = typename
std::enable_if<is_streamable<std::stringstream, C>::value, T>::type>
There are several thing you want:
a trait, is_streamable
a way to forbid class instantiation.
For the traits, you might use std::experimental_is_detected or run your own:
template <typename T>
auto is_streamable_impl(int)
-> decltype (T{},
void(), // Handle evil operator ,
std::declval<std::istringstream &>() >> std::declval<T&>(),
void(), // Handle evil operator ,
std::true_type{});
template <typename T>
std::false_type is_streamable_impl(...); // fallback, ... has less priority than int
template <typename T>
using is_streamable = decltype(is_streamable_impl<T>(0));
Then to forbid intantiation, several choices:
static_assert:
template <typename T>
class Input
{
static_assert(is_streamable<T>::value);
// ...
};
or SFINAE friendly class:
template <typename T, typename = std::enable_if_t<is_streamable<T>>>
class Input
{
// ...
};
so you allow to know if Input<T1> is valid.
Notice that without all that stuff, your program won't compile anyway when instantiating the problematic method (hard error, so no SFINAE friendly).
Being SFINAE friendly is not necessary most of the time.

c++ non-type parameter pack expansion

I am writing template function that is parametrized by single type, and has variable number of parameters of the same type (not of different types). It should check if first value is among the rest. I wanted to write it like this:
#include <unordered_set>
template <typename T>
static bool value_in(T val, T vals...) {
// compiles, but uses only vals[0]:
const std::unordered_set<T> allowed {vals};
// error: pack expansion does not contain any unexpanded parameter packs:
// const std::unordered_set<T> allowed {vals...};
return allowed.find(val) != allowed.end();
}
// usage
enum class Enumeration {one, two, three};
int main () {
// should return true -> 0
return value_in(Enumeration::two,
Enumeration::one,
Enumeration::two) ? 0 : 1;
}
I expected that second to work, but it doesn't compile because
test.cpp: In function ‘bool value_in(T, T, ...)’:
test.cpp:7:46: error: expansion pattern ‘vals’ contains no argument packs
I see the "(T, T, ...)" instead of "(T, T...)", so probably I messed up function declaration and ended with C-style variadic function.
How to write declaration that will accept arbitrary number of parameters of the same type?
First of all, defining a C-style variadic function
static bool value_in (T val, T vals, ...)
the comma before the ... is optional.
So your
static bool value_in(T val, T vals...)
define two not-variadic arguments (val and vals) and an unnamed variadic sequence.
How to write declaration that will accept arbitrary number of parameters of the same type?
There are many ways but, IMHO, with drawbacks
A possible way is the use of SFINAE: you can impose that the variadic types are equal to the first type.
The following is a C++17 possible solution that uses template folding
template <typename T, typename ... Ts>
std::enable_if_t<(std::is_same<T, Ts>::value && ...), bool>
value_in (T val, Ts ... vals)
{
const std::unordered_set<T> allowed {val, vals ... };
return allowed.find(val) != allowed.end();
}
You can develop this solution also in C++11/C++14 but is a little more complicated.
Drawback: the Ts... type are deduced and they must be exactly the same T type.
So if you want, by example, a function that accept a list of std::string(), you can't call it with a char const *
value_in(std::string{"abc"}, "123");
because T, std::string, is different from Ts..., char const *, and SFINAE doesn't enable value_in.
You can use std::is_convertible instead of std::is_same but I suggest another way, in two steps.
First of all you need a custom type traits (with using helper) to select the first type from a list
template <typename T, typename ...>
struct firstType
{ using type = T; };
template <typename T, typename ... Ts>
using firstType_t = typename firstType<T, Ts...>::type;
Now you can write a first step value_in() that intercept all values, detect al types (without restriction) and pass they to a second step function as follows
template <typename T, typename ... Ts>
bool value_in (T val, Ts ... vals)
{ return value_in_helper<T, Ts...>(val, vals...); }
The second step function change the all Ts... type in T using firstType
template <typename T, typename ... Ts>
bool value_in_helper (T val, firstType_t<T, Ts> ... vals)
{
const std::unordered_set<T> allowed {val, vals ... };
return allowed.find(val) != allowed.end();
}
This solution is C++11 compatible.
Drawback: you need a second step.
Advantage (IMHO): this solution pass through a second step function that is declared receiving T types so accept also arguments that are convertible to T.
That is: this solution accept also
value_in(std::string{"abc"}, "123");
because there isn't anymore needs that "123" is exactly a std::string; can also be convertible to std::string.
I see two options here. You can pass a std::initializer_list, which causes the function signature to change to
#include <initializer_list>
template <typename T>
static bool value_in(T&& val, std::initializer_list<T> vals)
{
/* Implementation as before */
}
and the calling snippet to
return value_in(Enumeration::two,
{ Enumeration::one, Enumeration::two }) ? 0 : 1;
Note the additional braces here, they are required to construct the initializer list to be passed. A little detail of this approach is the function signature, which immediately reveals that there is only one type to deduce.
If it feels wrong to type the braces, stick with your original attempt and tweak your function such that
template <typename S, typename... T>
static bool value_in(S&& val, T&&... vals) {
const std::unordered_set<S> allowed {std::forward<T>(vals)...};
/* As before... */
}
This allows for calling the function as in your original snippet. In contrast to the above solution, this signature obviously has two template parameters, which might require a second look to see that it will fail if S differs from T.