patterncppMinor
Typesafe scanf-like function with variadic templates
Viewed 0 times
withfunctionliketypesafetemplatesvariadicscanf
Problem
I'm hoping to get some feedback on a function with variadic templates that parses a format string and fills in some parameters whose order and types are based on the characters in the format string. Kind of like
(For the curious, I am trying to make a typesafe version of this function, documentation here.)
I'm pretty experienced with C but the last time I did anything serious with C++ was at university 15 years ago and it seems like a completely different language now.
This toy code works for me and fails in the ways I'd expect it to when I give it bad input. However, I'm not too familiar with the standard library so I have a strong feeling that I may be missing something. Also the code seems overly verbose for what it does.
So here's what I'm hoping to find out:
I am constrained to use C++11, the code currently does not use Boost.
```
#include
#include
struct SomeStruct {
int val;
};
template
bool
assign(const char c, T& ref) {
return false;
}
template<>
bool
assign(const char c, bool*& ref) {
if (c != 'b')
return false;
*ref = true;
return true;
}
template<>
bool
assign(const char c, int*& ref) {
if (c != 'i')
return false;
*ref = 42;
return true;
}
template<>
bool
assign(const char c, SomeStruct& ref) {
if (c != 'h')
return false;
ref.val = 81;
return true;
}
template
bool
tscanf(const char format, const char name, T& ref)
{
if (std::strlen(format) != 1) {
std::cerr
bool
tscanf(const char format, const char name, T& ref, Args... args)
{
if (!assign(format[0], ref)) {
std::cerr << "Wrong type for argument " << name << std::endl;
scanf().(For the curious, I am trying to make a typesafe version of this function, documentation here.)
I'm pretty experienced with C but the last time I did anything serious with C++ was at university 15 years ago and it seems like a completely different language now.
This toy code works for me and fails in the ways I'd expect it to when I give it bad input. However, I'm not too familiar with the standard library so I have a strong feeling that I may be missing something. Also the code seems overly verbose for what it does.
So here's what I'm hoping to find out:
- Is this code idiomatic modern C++?
- Am I overlooking useful features in the standard library that would make things easier?
- Should I be using pointers in this way, or am I thinking too much in C?
- Should I consider another approach entirely, like
tscanf(format, paramname1, paramname2) >> param1 >> param2?
I am constrained to use C++11, the code currently does not use Boost.
```
#include
#include
struct SomeStruct {
int val;
};
template
bool
assign(const char c, T& ref) {
return false;
}
template<>
bool
assign(const char c, bool*& ref) {
if (c != 'b')
return false;
*ref = true;
return true;
}
template<>
bool
assign(const char c, int*& ref) {
if (c != 'i')
return false;
*ref = 42;
return true;
}
template<>
bool
assign(const char c, SomeStruct& ref) {
if (c != 'h')
return false;
ref.val = 81;
return true;
}
template
bool
tscanf(const char format, const char name, T& ref)
{
if (std::strlen(format) != 1) {
std::cerr
bool
tscanf(const char format, const char name, T& ref, Args... args)
{
if (!assign(format[0], ref)) {
std::cerr << "Wrong type for argument " << name << std::endl;
Solution
So here's what I'm hoping to find out:
Is this code idiomatic modern C++?
Pretty much.
You use recursion to handle the multiple arguments. But a better technique may be to use the compiler to build objects to handle each argument. Unfortunately the way you have to defined the interface makes this a bit hard.
If we re-consider the interface to make it like C-scanf.
Am I overlooking useful features in the standard library that would make things easier?
I don't think there is anything you absolutely need.
Should I be using pointers in this way, or am I thinking too much in C?
NO. Absolutely do not use pointers like that.
Prefer to use references. This way you don't need to check for
Should I consider another approach entirely, like tscanf(format, paramname1, paramname2) >> param1 >> param2?
There have been a couple of attempts like this.
Is this code idiomatic modern C++?
Pretty much.
You use recursion to handle the multiple arguments. But a better technique may be to use the compiler to build objects to handle each argument. Unfortunately the way you have to defined the interface makes this a bit hard.
If we re-consider the interface to make it like C-scanf.
template
int parser(char const*& format, char const*& value, T& value)
{
// Parse format and value.
// convert the first % in format into value and save in value.
// return 1 if value was correctly parsed otherwise 0
}
template<typename... Args)
int tscanf(char const* format, char const* value, Args&... args)
{
// This will call parser() for each argument.
// Each of the parameters inside {} are guaranteed to be constructed
// left to right.
// Because the compiler knows the number of arguments you
// get a fixed size array that can be used to count the
// number of parameters that were actually found.
auto count[] = {parser(format, value, args)...};
// Probably need a check for unmatched format parts.
// The return the number of format parameters matched.
return std::accumulate(std::begin(count), std::end(count), 0);
}Am I overlooking useful features in the standard library that would make things easier?
I don't think there is anything you absolutely need.
Should I be using pointers in this way, or am I thinking too much in C?
NO. Absolutely do not use pointers like that.
Prefer to use references. This way you don't need to check for
null. You also express your intent much more clearly so there is no confusion on the ownership of the pointer.Should I consider another approach entirely, like tscanf(format, paramname1, paramname2) >> param1 >> param2?
There have been a couple of attempts like this.
boost::format springs to mind. They used % rather than >>. But personally I don't see a need for this. BUT big advancements have come from trying the non obvious (things I would not think of). So please experiment.Code Snippets
template<typename T>
int parser(char const*& format, char const*& value, T& value)
{
// Parse format and value.
// convert the first % in format into value and save in value.
// return 1 if value was correctly parsed otherwise 0
}
template<typename... Args)
int tscanf(char const* format, char const* value, Args&... args)
{
// This will call parser() for each argument.
// Each of the parameters inside {} are guaranteed to be constructed
// left to right.
// Because the compiler knows the number of arguments you
// get a fixed size array that can be used to count the
// number of parameters that were actually found.
auto count[] = {parser(format, value, args)...};
// Probably need a check for unmatched format parts.
// The return the number of format parameters matched.
return std::accumulate(std::begin(count), std::end(count), 0);
}Context
StackExchange Code Review Q#143599, answer score: 4
Revisions (0)
No revisions yet.