I'm doing some tests with std::initializer_list
. The idea is to use it instead of the classic {pointer-to-first, size} .
The test is simple: a list of menu items, generate it, and check the user input.
For the latter, I use a std::string
and calls to getline( )
.
For some reason that escapes me, if after getting user input I call size( )
, the program dies unexpectedly:
std::string userInput;
do {
getline( cin, userInput );
if( cin.eof( ) || cin.bad( ) ) { throw std::runtime_error( "Error al leer de std::cin !\n" ); }
if( userInput.empty( ) ) { printEntries( menu ); continue; }
if( userInput.size( ) != 2 ) { continue; }
^^ AQUÍ MUERE
...
However, if I comment out the last if( )
one (the one that checks for size to be != 2
), the code works fine.
How is this possible, if just in the previous line I check that userInput
it is not empty?
If I try to reproduce the problem with minimal code:
#include <iostream>
#include <string>
int main( ) {
std::string userInput;
std::getline( std::cin, userInput );
if( std::cin.eof( ) || std::cin.bad( ) ) { throw std::runtime_error( "Error al leer de std::cin !\n" ); }
if( userInput.empty( ) ) { std::cout << "Mostrar menu otra vez\n"; }
if( userInput.size( ) != 2 ) { std::cout << "Entrada incorrecta\n"; }
return 0;
}
The error is not reproduced .
With the debugger, in the problematic code and just before the call, I notice that the content of userInput
is correct: it always contains the last read of std::cin
:
The truth is that I can not think of the reason for this behavior.
The full code:
#include <algorithm>
#include <initializer_list>
#include <iostream>
struct MenuEntry {
char key;
const char *label;
void ( *action )( );
};
using MenuList = std::initializer_list< MenuEntry >;
static void doExit( ) {
std::cout << "\nFin del programa.\n";
::exit( 0 );
}
const MenuList MainMenuEntries = {
{ '0', "Salir", doExit }
};
static const MenuEntry *manageMenu( MenuList menu ) {
using std::cout;
using std::cin;
auto printEntries = []( MenuList menu ) {
for( const auto &item : menu ) {
cout << " " << item.key << ". " << item.label << '\n';
}
};
printEntries( menu );
std::string userInput;
const MenuEntry *select;
do {
getline( cin, userInput );
if( cin.eof( ) || cin.bad( ) ) { throw std::runtime_error( "Error al leer de std::cin !\n" ); }
if( userInput.empty( ) ) { printEntries( menu ); continue; }
if( userInput.size( ) != 2 ) { continue; }
select = std::find_if( menu.begin( ), menu.end( ), [&userInput]( const MenuEntry &me ){
return userInput[0] == me.key;
} );
} while( select == menu.end( ) );
return select;
}
int main( ) {
while( true ) {
const MenuEntry *userAction = manageMenu( MainMenuEntries );
std::cout << "Elegido " << userAction->label;
userAction->action( );
}
return 0;
}
It compiles perfectly with g++ -std=c++11 -Wall -Wextra -pedantic -g -ggdb -g3 -o test test.cpp
, without warnings or errors.
g++ version:g++ (Ubuntu 8.3.0-6ubuntu1~18.10.1) 8.3.0
For that there are stl containers, such as
std::vector
. The utility ofstd::initializer_list
is to allow the initialization of objects on the fly , in fact the documentation makes it very clear how limited its use is:Which more or less comes to say:
That is, these objects, mainly, to use and throw away. They are temporary and it is not convenient to use them for other purposes.
But where do you say the problem may be?
I would bet that the problem lies in this line:
Because if we look at the declaration of
manageMenu
we see that a copy of is being madeMenuList
, which is the initialization list:Replacing
std::initializer_list
withstd::vector
, for example, doesn't require too many changes: