error: invalid user defined conversion

I'm attempting to implement a lambda to return the largest number of characters in an arbitrary group of strings. As each call of to the lambda may be different, I wish to employ a variadic lambda. When compiling the code below, I receive a few errors.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Lambda used for reporting the largest of the string sizes
size_t biggest = [](...) -> size_t // Always pass "\0" (a null string) as the last argument
    {
        size_t large = 0;
        va_list l;
        va_start(l, 0);
        string s;

        while (true) // Sentinel terminated loop
        {
            s = va_arg(l, string);
            if (s == "\0")
                break;
            if(s.length() > large)
                large = s.length();
        }
        va_end(l);
        return large;
    };


|19|error: invalid user-defined conversion from ‘State::printTable(std::ostream&, int, int, helmholtz::num_t, helmholtz::num_t, helmholtz::num_t, helmholtz::num_t)::<lambda(...)>’ to ‘size_t’ {aka ‘long unsigned int’} [-fpermissive]|
|19|sorry, unimplemented: converting lambda that uses ‘...’ to function pointer|

I've not used lambdas before, and I thought this might be a simple enough version to impliment.
Last edited on
To get rid of the error you should change the type of biggest from size_t to auto.

 
auto biggest = [](...) -> size_t ...

Note that va_arg can't handle complex class types like std::string, but C-style strings (const char*) is alright.

You can make it work with std::string by using a parameter pack (https://en.cppreference.com/w/cpp/language/parameter_pack) but I think it's a bit more complicated (I don't even know how to do it without creating extra functions but maybe it's possible using fold expressions). On the positive side, if you make it work, you wouldn't need to pass something extra like "\0" to mark the end of the arguments.
Last edited on
As I've been looking, I thought using the parameter pack would probably be the way to go, but that's also something I know little about. When I learned and predominantly used C++, it was around 2010. I've not used it much since then. These lambdas and parameter packs are all new to me.

If anyone could give guidance on how to convert this over to a parameter pack, I'd greatly appreciate that.
It's relatively easy to write it as a regular variadic function template.

1
2
3
4
5
6
7
8
9
10
std::size_t biggest(const std::string& last)
{
	return last.length();
}

template <typename ... Rest>
std::size_t biggest(const std::string& first, Rest ... rest)
{
	return std::max(first.length(), biggest(rest...));
}
Last edited on
Thanks for your help.

The reason I'm trying to implement this as a lambda is that I only need this capability for one function in a class. I desire not to separate this out and have it (organizationally) dangling as an unrelated (as compared to the overall class purpose) member or non-member function if possible.
You could do this:
1
2
auto max_size = [](auto... args)
{ return std::max({args.size()...}); };

This calls the initializer_list overload of std::max.

Anyways:
1. It makes sense to support perfect forwarding here.
2. This violates a guideline of generic programming by saying x.size() instead of using std::size and calling size(x), but this is deliberate, because calling this with a string literal would obtain the array size - not the length of the string.

There are various ways to work around this, but a solution needs to be aware of the problem domain.
Last edited on
Ah, so that's how you write it...

Looks like std::string_view might be useful here.

1
2
auto max_size = [](const auto& ... args)
{ return std::max({std::string_view{args}.size()...}); };

Now you can pass string literals if you want.

Edit: Changed it to const auto& ... because we don't want to create unnecessary copies of the strings.
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream> // std::cout,...
#include <cstdarg>  // va_list, va_start, va_arg, va_end
#include <cstring>  // strlen
int main()
{
    auto biggest = [](const char * first, ...)->size_t{
        size_t large = 0;
        char *s = const_cast<char *>(first);
        va_list l;
        va_start(l, first);
        do{
            large = std::max(large, strlen(s));
            s = va_arg(l, char *);
        } while(s != NULL);
        va_end(l);
        return large;
    };
    
    std::cout <<"Biggest = "
              <<biggest("test","biggest","now", NULL)
              <<std::endl;
   return 0;
}


$g++ -std=c++11 -o main *.cpp
$main
Biggest = 7
mbozzi, could you elaborate on what you mean by "violates a guideline of generic programming"? I'm brand new to these concepts I'm implementing, in particular lambdas and variadic arguements (outside main()). I'm unfamiliar with the conventions associated with them.
I think I was too hasty to make that claim.

In any event, "generic" code is code that's supposed to work with more than one type.

Many excellent examples can be found in the standard library. For example, std::vector is generic, since we can put almost anything in a vector. Indeed, the authors of std::vector took great effort making sure it works with as many types as possible, because this improves flexibility and reduces code duplication.

In general, generic code has some interest in being able to handle as many different things as possible. This is done by minimizing the requirements imposed on the inputs.

For example, the lambda that looks like this
1
2
auto max_size = [](auto... args)
{ return std::max({args.size()...}); };

Does a okay job of finding the largest container from its arguments. But it requires that all of args have a size member function that returns something copyable and comparable; and that all of args.size() has the same type, and all of args can be copied, and some other things.

Semantically, however, the minimum we need out of an argument to max_size is that
a.) we can get a size
b.) we can compare two sizes.

If we really wanted to eliminate all the unnecessary requirements we would obtain code only a language expert can understand. But just to be able to pass in things like arrays is perhaps desirable. Right now we can never do it, because an array never has a size member function. Switching from a member to a free-function gains us some flexibility for cheap, because we can overload or specialize free functions / function templates however we want.

1
2
3
4
5
6
using std::size;
std::size_t size(char const* str)
{ return std::strlen(str); }

auto biggest = [](auto... args)
{ return std::max({size(args)...}); };


How you solve this problem depends on what the intent is. You're probably best following Peter87's suggestion here, unless you're concerned with passing things other than strings.
Last edited on
Topic archived. No new replies allowed.