Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

how to make a simple generic function... parameterized by collection & value

51 views
Skip to first unread message

Mark Summerfield

unread,
May 17, 2012, 9:45:32 AM5/17/12
to
This is a followup to a mailing with the same subject. I had v.
helpful replies from wasti.r...@gmx.net and Daniel Krügler and have
now progressed to this:

template<typename T>
using Validator = std::function<T(*)(const std::string&)>;

template<typename C>
Validator<typename C::value_type>
makeSetValidator(const C &validItems,
Validator<typename C::value_type> validate)
{
return [=](const std::string &s)->typename C::value_type{
const auto &x = validate(s); // returns valid item of correct
type or throws
if (std::find(std::begin(validItems), std::end(validItems),
x))
return x;
throw ValueError("Invalid item '" + s + "'");
};
}

The purpose of the code is to be able to supply a collection of valid
items and a per-item validation function and to get back a new
validation function. The returned function is expected to be called
with an item which must then be validated in two ways (1) by calling
the captured validate() function and (2) by ensuring that the item is
in the collection of valid items.

Unfortunately, although this compiles, it doesn't work because
compilation fails at the call site. For example:

auto v = makeSetValidator<int>(std::set<int>{-4, 8, 31},
validate<int>);

The validate() function is a template function:

template<typename T>
T validate(const std::string &s) { ... }

g++ 4.7.0 says:

main.cpp: In function ‘void test9(const string&)’:
main.cpp:318:54: error: no matching function for call to
‘makeSetValidator(std::set<int>, <unresolved overloaded function
type>)’
main.cpp:318:54: note: candidate is:
In file included from optarg_option.hpp:17:0,
from optarg.hpp:33,
from main.cpp:14:
optarg_validator.hpp:98:1: note: template<class C>
OptArg::Validator<typename C::value_type>
OptArg::makeSetValidator(const C&, OptArg::Validator<typename
C::value_type>)
optarg_validator.hpp:98:1: note: template argument deduction/
substitution failed:
optarg_validator.hpp: In substitution of ‘template<class C>
OptArg::Validator<typename C::value_type>
OptArg::makeSetValidator(const C&, OptArg::Validator<typename
C::value_type>) [with C = int]’:
main.cpp:318:54: required from here
optarg_validator.hpp:94:58: error: ‘int’ is not a class, struct, or
union type
optarg_validator.hpp:94:58: error: ‘int’ is not a class, struct, or
union type

Note that it also fails if I provide a set<std::string> and use
validate<std::string> as the validator function.


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Daniel Krügler

unread,
May 17, 2012, 9:03:31 PM5/17/12
to
Am 17.05.2012 15:45, schrieb Mark Summerfield:
> This is a followup to a mailing with the same subject. I had v.
> helpful replies from wasti.r...@gmx.net and Daniel Krügler and have
> now progressed to this:
>
> template<typename T>
> using Validator = std::function<T(*)(const std::string&)>;

This is an invalid specialization of std::function. Did you mean
std::function<T(const std::string&)> ?

> template<typename C>
> Validator<typename C::value_type>
> makeSetValidator(const C&validItems,
> Validator<typename C::value_type> validate)
> {
> return [=](const std::string&s)->typename C::value_type{
> const auto&x = validate(s); // returns valid item of correct

This looks dangerous to me: You are binding here a temporary to a reference.

> type or throws
> if (std::find(std::begin(validItems), std::end(validItems),
> x))

How should this be well-formed? std::find returns an iterator. Did you mean

if (std::find(std::begin(validItems), std::end(validItems), x) !=
std::end(validItems))

instead?

> return x;
> throw ValueError("Invalid item '" + s + "'");
> };
> }
>
> The purpose of the code is to be able to supply a collection of valid
> items and a per-item validation function and to get back a new
> validation function. The returned function is expected to be called
> with an item which must then be validated in two ways (1) by calling
> the captured validate() function and (2) by ensuring that the item is
> in the collection of valid items.
>
> Unfortunately, although this compiles,
>
> it doesn't work because
> compilation fails at the call site. For example:
>
> auto v = makeSetValidator<int>(std::set<int>{-4, 8, 31},
> validate<int>);
>
> The validate() function is a template function:
>
> template<typename T>
> T validate(const std::string&s) { ... }

This cannot work: You are forcing the compiler to deduce the template
parameter to be int, but you seems to expect a container-like type. The
explicit template-argument should be removed, because C should be
deduced to std::set<int>.
Your code is plain buggy, but in addition there is also a gcc bug in
regard to non-deduced contexts, see:

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52072

A workaround should be to introduce the following helper types

template<class T>
struct identity { typedef T type; };

template<class T>
using NonDeduced = typename identity<T>::type;

and to redeclare your function template as

template<typename C>
Validator<typename C::value_type>
makeSetValidator(const C& validItems,
NonDeduced<Validator<typename C::value_type>> validate);

HTH & Greetings from Bremen,

Daniel Krügler

Mark Summerfield

unread,
May 18, 2012, 9:03:14 AM5/18/12
to
Hi Daniel,

Is what I'm trying to do very much against the grain of C++11? Or is
it that I'm taking the wrong approach?

Scenario: I have validator functions that take a string and return an
object of a given type, e.g., int validator(string); string
validator(string); bool validator(string) --- or throws if the string
can't be converted to the required type (e.g., for int the string
doesn't represent a number or is too big or too small). These work
fine. But I also want to create a validator that takes an existing
validator plus a collection of specific values and returns a new
validator that does the conversion (or throws) and if it hasn't thrown
then checks to see if the converted item is in the collection.

I changed the code along the lines you suggested:

template<typename T>
struct identity { typedef T type; };
template<typename T>
using NonDeduced = typename identity<T>::type;

template<typename T>
using Validator = std::function<T(const std::string)>;

template<typename C>
Validator<typename C::value_type>
makeSetValidator(const C &validItems,
NonDeduced<Validator<typename C::value_type>>
validate)
{
return [=](const std::string s)->typename C::value_type{
const auto x = validate(s);
if (std::find(std::begin(validItems), std::end(validItems), x)
!= std::end(validItems))
return x;
throw ValueError("Invalid item '" + s + "'");
};
}

I also tried changing the call site:

auto v = makeSetValidator(std::set<int>{-4, 8, 31}, validate);

But g++ 4.7.0 gave me this:

main.cpp: In function �void test9(const string&)�:
main.cpp:318:49: error: cannot resolve overloaded function �validate�
based on conversion to type
�OptArg::NonDeduced<std::function<int(std::basic_string<char>)> > {aka
std::function<int(std::basic_string<char>)>}�
main.cpp: In function �void test10(const string&)�:
main.cpp:356:42: error: no matching function for call to
�makeSetValidator(std::set<std::basic_string<char> >, <unresolved
overloaded function type>)�
main.cpp:356:42: note: candidate is:
In file included from optarg_option.hpp:17:0,
from optarg.hpp:33,
from main.cpp:14:
optarg_validator.hpp:104:1: note: template<class C>
OptArg::Validator<typename C::value_type>
OptArg::makeSetValidator(const C&,
OptArg::NonDeduced<std::function<typename
C::value_type(std::basic_string<char>)> >)
optarg_validator.hpp:104:1: note: template argument deduction/
substitution failed:
main.cpp:356:42: note: cannot convert
�std::set<std::basic_string<char>
>(std::initializer_list<std::basic_string<char> >{((const
std::basic_string<char>*)(& const std::basic_string<char> [2]
{std::basic_string<char>(((const char*)"a"), (*(const
std::allocator<char>*)(& std::allocator<char>()))),
std::basic_string<char>(((const char*)"bee"), (*(const
std::allocator<char>*)(& std::allocator<char>())))})), 2u}, (*(const
std::less<std::basic_string<char> >*)(&
std::less<std::basic_string<char> >())), (*(const allocator_type*)(&
std::allocator<std::basic_string<char> >())))� (type
�std::set<std::basic_string<char> >�) to type �const
std::basic_string<char>&�

Thanks!

Daniel Krügler

unread,
May 18, 2012, 5:15:08 PM5/18/12
to
Am 18.05.2012 15:03, schrieb Mark Summerfield:
> Is what I'm trying to do very much against the grain of C++11? Or is
> it that I'm taking the wrong approach?
>
> Scenario: I have validator functions that take a string and return
> an object of a given type, e.g., int validator(string); string
> validator(string); bool validator(string) --- or throws if the
> string can't be converted to the required type (e.g., for int the
> string doesn't represent a number or is too big or too small). These
> work fine. But I also want to create a validator that takes an
> existing validator plus a collection of specific values and returns
> a new validator that does the conversion (or throws) and if it
> hasn't thrown then checks to see if the converted item is in the
> collection.

I'm not sure what you are asking for. It seems that your function
template makeSetValidator does what you want to realize. You seem to
have some assumptions about template deduction that aren't valid,
though, for details see below.

> I changed the code along the lines you suggested:
>
> template<typename T>
> struct identity { typedef T type; };
> template<typename T>
> using NonDeduced = typename identity<T>::type;
>
> template<typename T>
> using Validator = std::function<T(const std::string)>;

Btw.: I wasn't suggestion to change your signature T(const
std::string&) to T(const std::string).

> template<typename C>
> Validator<typename C::value_type>
> makeSetValidator(const C&validItems,
> NonDeduced<Validator<typename C::value_type>>
> validate)
> {
> return [=](const std::string s)->typename C::value_type{
> const auto x = validate(s);

Just as a remark: Your previous form binding the result of the
validate call by reference was valid, because the reference is
life-time extended. But I think your new code is more idomatic than
before (There is no real advantage of binding to a reference and such
code is questionable at best).

> if (std::find(std::begin(validItems), std::end(validItems), x)
> != std::end(validItems))
> return x;
> throw ValueError("Invalid item '" + s + "'");
> };
> }
>
> I also tried changing the call site:
>
> auto v = makeSetValidator(std::set<int>{-4, 8, 31}, validate);
>
> But g++ 4.7.0 gave me this:

The error is understandable. It becomes easier to understand this,
when you simplify your example to the following form:

Validator<int> v = validate;

I expect the same kind of error here. The problem is easy to
understand: As defined, the template parameter of validate cannot be
deduced without further information. A possible way to provide this
context information is to specify the *exact* function pointer type,
like so:

int(*p)(const std::string&) = validate;

But std::function<X> is not function pointer type, nor is it a type
that requires a special function pointer of function type X. It
accepts any form of Callable type that is "compatible" with the
function type X, especially conversions of the parameter types of X or
of the return type of X are all supported. The relevant constructor of
std::function is this one:

template<class F> function::function(F);

Note that this is also a template, so we have a catch-22 situation here:
You want to have the return type of template validate deduced given a
template that needs the necessary information to deduce template
parameter F here.

I see no way except that you somehow specify the return type of
validate as in your original form. Alternatively you could provide a
further overload of makeSetValidator that accepts a special function
pointer type, like so:

template<typename C>
Validator<typename C::value_type>
makeSetValidator(const C& validItems,
NonDeduced<typename C::value_type(*)(const
std::string&)> validate);

with the same definition as your current definition. (Above form
assumes that your validate function has the following declaration

template<typename T>
T validate(const std::string &s);

HTH & Greetings from Bremen,

Daniel Krügler




Mark Summerfield

unread,
May 19, 2012, 4:28:43 AM5/19/12
to
Hi Daniel,

I tried what you suggested but got the same errors.

template<typename T>
struct identity { typedef T type; };
template<typename T>
using NonDeduced = typename identity<T>::type;

template<typename T>
using Validator = std::function<T(const std::string)>;

template<typename C>
Validator<typename C::value_type>
makeSetValidator(const C &validItems,
NonDeduced<typename C::value_type(*)(const std::string)> validate)
{
return [=](const std::string s)->typename C::value_type{
const auto x = validate(s);
if (std::find(std::begin(validItems), std::end(validItems), x)
!= std::end(validItems))
return x;
throw ValueError("Invalid item '" + s + "'");
};
}

template<typename T>
T validate(const std::string s) { ... }

At the call site I tried:

auto v = makeSetValidator<int>(std::set<int>{-4, 8, 31},
validate<int>);
auto v = makeSetValidator(std::set<int>{-4, 8, 31}, validate<int>);
auto v = makeSetValidator<int>(std::set<int>{-4, 8, 31}, validate);
auto v = makeSetValidator(std::set<int>{-4, 8, 31}, validate);

None of which would compile.

I think I'll give up now:-)

Thanks!

Daniel Krügler

unread,
May 19, 2012, 4:42:09 PM5/19/12
to
{ This thread has turned into a debugging effort of a specific piece
of code, which limits its relevance to the general C++ community.
Please keep that in mind when replying -mod }
The first and third form cannot possibly be well-formed for reasons
that I explained in my very first reply in this thread. The other
forms should work. Since you do not provide a complete program, but
only snippets there might be several reasons for the failure (include
that the compiler version you used cannot properly handle some of the
new C++11 constructs). Except from the lambda expression, it is easy
to make your code a fully C++03 compatible program like so:

#include <string>
#include <set>

template<typename T>
struct identity { typedef T type; };

struct ValueError { ValueError(const std::string& what) {} };

template<typename T>
struct function;

template<typename R, typename T>
struct function<R(T)>
{
template<typename F>
function(F){}
};

template<typename C>
function<typename C::value_type(const std::string)>
makeSetValidator(const C &validItems,
typename identity<typename C::value_type(*)(const
std::string)>::type validate)
{
return function<typename C::value_type(const std::string)>(validate);
}

template<typename T>
T validate(const std::string s) { return T(); }

int main()
{
makeSetValidator(std::set<int>(), validate<int>);
makeSetValidator(std::set<int>(), validate);
}

This compiles on every compiler I tested. I recommend to start with
this and to increase the functionality step-wise to the point where it
does no longer work, then try to isolate the actual problem.

HTH & Greetings from Bremen,

Daniel Krügler



0 new messages