Why is a function with default-assigned argument(s) not accepted as 0-arg Generator?

Viewed 83

The following code attempts to use ns::generateInt() as the Generator argument to std::generate_n():

// main.cpp

#include <vector>
#include <algorithm>
#include <ctime>

static int _tmp = ( srand( time( NULL ) ), 0 );

namespace ns
{

  int generateInt( int offset = 0 )
  {
    return offset + rand() % 128;
  }

}

int main( int argc, char* argv[] )
{
  std::vector<int> v;
  int tmp = ns::generateInt(); // Fine to call ns::generateInt() w/ 0 args

  std::generate_n( std::back_inserter( v ),
                   5,
                   ns::generateInt ); // Not accepted as 0-arg Generator
  return 0;
}

This code generates the following compile error:

$ g++ --version && g++ ./main.cpp
g++ (GCC) 9.2.1 20190827 (Red Hat 9.2.1-1)
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

In file included from /usr/include/c++/9/algorithm:62,
                 from ./main.cpp:4:
/usr/include/c++/9/bits/stl_algo.h: In instantiation of ‘_OIter std::generate_n(_OIter, _Size, _Generator) [with _OIter = std::back_insert_iterator<std::vector<int> >; _Size = int; _Generator = int (*)(int)]’:
./main.cpp:26:36:   required from here
/usr/include/c++/9/bits/stl_algo.h:4493:18: error: too few arguments to function
 4493 |  *__first = __gen();
      |             ~~~~~^~

I know that the std::generate_n() signature takes a Generator function/functor that takes 0 arguments, but the argument to ns::generateInt() has a default value; and as shown above, in non-templated code, I can invoke ns::generateInt() with no arguments.

If the only change made to the above code is this...

  int generateInt( /*int offset = 0*/ )
  {
    return /*offset +*/ rand() % 128;
  }

...then it compiles just fine:

$ g++ ./main.cpp
$

What's going on from the compiler's perspective here? My default-argumented generator function is invokable with 0 arguments, so why is it not usable as the Generator argument to std::generate_n()?

3 Answers

Just because generateInt() has a default argument value does not change the fact that it has an argument, which you can see in the error message:

_Generator = int (*)(int)

The default argument value is not part of the function's type.

When you call generateInt() directly, the compiler knows the full declaration of generateInt() and so it knows that it can allow you to omit the defaulted argument value.

But, inside of std::generate_n(), the compiler does not know that the _Generator __gen input argument is pointing at your generateInt() function. All it knows is that __gen is pointing at some function whose type takes an int argument, any knowledge of a default value is lost. And since std::generate_n() is not supplying that argument value in the call to __gen(), you get the compiler error.

The Generator template parameter of std::generate_n() requires a Callable type that takes no arguments. So, the only way you can use your 1-argument generateInt() as-is with std::generate_n() is to wrap it inside a 0-argument lambda or functor, eg:

std::generate_n( std::back_inserter( v ),
                   5,
                   []{ return ns::generateInt(/*0*/); } );
struct genInt
{
    int operator()(){ return ns::generateInt(/*0*/); }
};

std::generate_n( std::back_inserter( v ),
                   5,
                   genInt() );

From the reference for std::generate_n:

g - generator function object that will be called. The signature of the function should be equivalent to the following:

Ret fun();

Note that it says the signature must be Ret fun();, not that the argument is invokable with 0 arguments.

You could wrap the call in a lambda quite easily so as not to change the code in ns:

std::generate_n(std::back_inserter(v),
                5,
                [] { return ns::generateInt(); }); 

Default arguments do not alter the signature of a function. The signature still contains the argument types that have default values. When a function takes an argument that is a pointer to another function requiring no arguments, but the argument is given a pointer to a function that does take arguments (with or without default values), then you have a type mismatch.

Related