Reputation: 59763
I'm trying to make a C++ function that accepts an unknown number of parameters total, but that they are always paired with specific types.
// logically, this is what the template Pair would be
// template<int, std::string> struct Pair {};
// desired:
// accept a const char * as a first parameter, and then in pairs ...
// integer, const char *
template <typename... Arguments> unsigned int onlyInPairs
(const std::string name, const Arguments& ... args) {
const unsigned numargs = sizeof...(Arguments);
// more magic would happen here with the parameters :)
return numargs;
}
int _tmain(int argc, _TCHAR* argv[])
{
// only string, [num, string] [num, string] should work
// desire that the syntax be as simple as shown, and not require
// extra classes to be created (like a Tuple) for each pair.
// this should work...
auto count = onlyInPairs("ABC", 1, "DEF", 2, "HIJ"); // works
// this should not work, as it's not number, string
count = onlyInPairs("ABC", 1, "DEF", "NOTRIGHT", 2);
return 0;
}
I've looked at parameter packs (reference), but can't seem to apply the documentation I've found to my specific problem. I'd like to try to catch the problem at compile time if the parameters are not specified correctly.
The goal was to use a syntax that was free of template noise as much as possible as the "pairs" will always be this way (and the programmer will know that). So, we wanted to just have int, string (repeat).
Ideally, the solution would work with Visual Studio 2013's C++ compiler, but I'd accept any answer that works and demonstrates the current possible shortcomings of VS C++ related to this issue.
The code being written would ultimately be often read by tech-savvy, but not formally trained C/C++ programmers (like a technical support). So, we're trying to get it to be distraction free as much as possible. There can be 2
-16
pairs of values ... so keeping it distraction free and just the data is desirable.
Upvotes: 0
Views: 778
Reputation: 55395
Here's one possibility. Class template Enforce
recursively inherits from itself and applies static_assert
on pairs of template arguments until the specialization is picked that doesn't do anything:
#include <type_traits>
#include <string>
template<typename...Args>
struct Enforce;
template<typename T, typename T1, typename T2, typename... Args>
struct Enforce<T, T1, T2, Args...> : Enforce<T, Args...> {
static_assert( std::is_constructible<T, T2>::value, "Wrong T2!");
};
template<typename T>
struct Enforce<T> {
};
template <typename... Arguments>
void onlyInPairs (const std::string name, const Arguments& ... args)
{
Enforce<std::string, Arguments...>();
}
int main()
{
onlyInPairs("this", 1, "works", 2, "fine");
//onlyInPairs("this", 1, "doesn't", 2, 3);
}
Instead of recursive inheritance, you can use recursive typedef instead. At least in gcc, that ought to compile faster and with less noise (warning about non-virtual destructor in base class, etc.).
EDIT:
Here's another version that ANDs the checks together and saves the result:
template<typename...Args>
struct Enforce;
template<typename T, typename T1, typename T2, typename... Args>
struct Enforce<T, T1, T2, Args...> {
static const bool value =
std::is_constructible<T,T2>::value &&
Enforce<T, Args...>::value;
};
template<typename T>
struct Enforce<T> : std::true_type {
};
Now you can move the assert closer, inside onlyInPairs
:
template <typename... Arguments>
void onlyInPairs (const std::string name, const Arguments& ... args)
{
static_assert( Enforce<std::string, Arguments...>::value , "Wrong second arg..." );
}
Upvotes: 2
Reputation: 1997
Use compile time recursion:
void processArgPairs() {
// to stop recursion
}
template <typename Arg1, typename Arg2, typename... Arguments>
void processArgPairs(Arg1 a, Arg2 b, Arguments&& ...args){
static_assert(std::is_constructible<int, Arg1>::value, "Wrong type of first argument - int expected");
static_assert(std::is_constructible<std::string, Arg2>::value, "Wrong type of second argument - string expected
processArgPairs(std::forward<Arguments>(args)...);
}
template <typename... Arguments> unsigned int onlyInPairs
(const std::string name, Arguments&& ... args) {
const unsigned numargs = sizeof...(Arguments);
processArgPairs(std::forward<Arguments>(args)...);
return numargs;
}
Upvotes: 1
Reputation: 13486
What template noise do you speak of?
void onlyInPairs(std::initializer_list<std::pair<int, std::string>>&& pairs) {}
int main() {
onlyInPairs({
{1, "abc"},
{2, "def"},
{3, "foo"},
});
}
Upvotes: 1
Reputation: 67723
This is a fairly old-school solution: using is_convertible should be cleaner
#include <string>
template <typename... Args> struct EnforcePairsHelper;
// terminal case
template <> struct EnforcePairsHelper<> {
enum { size = 0 };
};
// multiple specializations for reliable matching:
// only the last is really required here
template <typename... ArgTail>
struct EnforcePairsHelper<int, const char *, ArgTail...> {
enum { size = 2 + EnforcePairsHelper<ArgTail...>::size };
};
template <typename... ArgTail>
struct EnforcePairsHelper<int, char *, ArgTail...> {
enum { size = 2 + EnforcePairsHelper<ArgTail...>::size };
};
template <int N, typename... ArgTail>
struct EnforcePairsHelper<int, char [N], ArgTail...> {
enum { size = 2 + EnforcePairsHelper<ArgTail...>::size };
};
template <typename... Args> unsigned onlyInPairs (const std::string name,
const Args& ... args) {
const unsigned numargs = EnforcePairsHelper<Args...>::size;
// more magic would happen here with the parameters :)
return numargs;
}
int main() {
unsigned ok = onlyInPairs("ABC", 1, "DEF", 2, "HIJ");
// unsigned no = onlyInPairs("ABC", 1, "DEF", "NOTRIGHT", 2);
}
Upvotes: 0
Reputation: 8805
Something like this?
template <typename... Arguments>
unsigned int onlyInPairs(const std::string name, const Arguments& ... args)
{
const unsigned numargs = sizeof...(Arguments);
check(args...);
return numargs;
}
template <typename... Arguments>
void check(const int i, const std::string name, const Arguments& ... args)
{
check(args...);
}
void check(const int i, const std::string name)
{
}
int main()
{
auto count = onlyInPairs("ABC", 1, "DEF", 2, "HIJ"); // works
count = onlyInPairs("ABC", 1, "DEF", "NOTRIGHT", 2); //compile error
return 0;
}
Upvotes: 0