Thread: Lettin the user specify initial conditions

  1. #1
    Kiss the monkey. CodeMonkey's Avatar
    Join Date
    Sep 2001
    Posts
    937

    Lettin the user specify initial conditions

    Hello,
    --- I'm rewriting the "settings" portion of an application. I had implemented it hackishly before, and now maintaining it is becoming embarrassing. The trick is that I have three different types of variables (albeit mostly integral) that I'd like the user to be able to set from the command line. However, I'd like to do this in a way that I could arbitrarily increase the number of types in consideration.
    --- Mostly the user sets flags, and so that's a set<string, bool>, which works nicely. But other times I want to encounter input like "set_sub-period_4", and know to set the variable labeled sub-period to the value 4.
    --- My best idea so far is to have a class setting that contains a string, a void pointer, and a typeid. Then I'll have some function with a bunch of if/else statements testing 'type == typeid(int)...... type == typeid(double)....'
    --- Is there a brief way to do this while avoiding blocks of 'if' checks? Fortunately, I am limited to a small set of types.

    Any input is appreciated. Also, boost::any doesn't seem to help in this case.
    "If you tell the truth, you don't have to remember anything"
    -Mark Twain

  2. #2
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    What's wrong with getopt or a getopt-like setup?

    (Edit to add: after all at some point you're going to have to do some if-checks to make sure the variable we're trying to adjust exists; whether it's adding all our variables to maps somewhere and doing find or doing an if for each flag seems roughly the same to me.)
    Last edited by tabstop; 06-19-2009 at 09:08 PM.

  3. #3
    Kiss the monkey. CodeMonkey's Avatar
    Join Date
    Sep 2001
    Posts
    937
    I'll look into getopt. I was wondering if there's any accepted way to do it.
    "If you tell the truth, you don't have to remember anything"
    -Mark Twain

  4. #4
    Registered User
    Join Date
    Dec 2007
    Posts
    2,675
    Boost's Program Options is nice.

  5. #5
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,057
    I've written several argument parsers before . . . my favourite C++ design goes something like this. You use a std::map from std::strings to function pointers (or function objects) which take the parameter as an argument and so set the appropriate variable. Special arguments you can then handle specially (with special functions/objects), and the ordinary arguments you can handle with something like this:
    Code:
    class ArgumentHandler {
    public:
        virtual ~ArgumentHandler() {}
        virtual void handle(std::string value) = 0;
    };
    
    template <typename Type>
    class ArgumentHandlerNormal : public ArgumentHandler {
    private:
        Type &variable;
    public:
        ArgumentHandlerNormal(Type &variable) : variable(variable) {}
    
        virtual void handle(std::string value) {
            std::istringstream stream(value);
            if(stream >> variable) return;
            // throw ....
        }
    };
    
    // somewhere else ...
    std::map<std::string, ArgumentHandler *> handlers;
    
    int debug_level = 0;  // default debug level
    handlers["debug"] = new ArgumentHandlerNormal<int>(debug_level);
    Anyway, I'm not sure if that's a "socially acceptable" way of doing it, but you could try it. Take it, polish it. (You'd probably want the map to go in a class which could automatically free the Handlers; you may want to create a Setting class instead of using primitives -- a Setting class could provide better support for default values; you may want to pass the entire list of arguments to ArgumentHandlers so that an option can take more than one parameter . . . .)

    Yes, I'm sure there's a library for this, but if you're like me you'll have fun implementing it yourself.

    [edit] Or use that boost library and spoil all the fun. [/edit]
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  6. #6
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Yet another possibility:

    Code:
    #include <map>
    #include <string>
    #include <sstream>
    #include <iostream>
    
    using namespace std;
    
    struct properties
    {
    	void put( string const& name, string const& value = string( ) )
    	{
    		data[ name ] = value;
    	}
    
    	template < typename Type >
    	bool get( string const& name, Type* value )
    	{
    		if( data.find( name ) == data.end( ) )
    			return false;
    		stringstream
    			stream;
    		stream << data[ name ];
    		if( !( stream >> *value ) )
    			return false;
    		return true;	
    	}
    
    	bool extract( string const& text )
    	{
    		stringstream
    			stream;
    		stream << text;
    		string
    			name;
    		getline( stream, name, '=' );
    		if( name.empty( ) )
    			return false;
    		string
    			value;
    		getline( stream, value );
    		if( value.empty( ) )
    			return false;
    		put( name, value );
    		return true;	
    	}
    	
    	map< string, string >
    		data;
    };
    
    int main( int argc, char** argv )
    {
    	char const*
    		names[ ] = 
    	{
    		"output-file-name", 
    		"sub-period", 
    		"factor"
    	};
    	properties
    		settings;
    	for( int index = 0; index < sizeof( names ) / sizeof( names[ 0 ] ); ++index )
    		settings.put( names[ index ] );
    	while( *( ++argv ) )
    	{
    		string
    			command = *argv;
    		if( settings.extract( command ) )
    			continue;
    		if( command.find( "=" ) != string::npos )
    		{
    			cerr << "Error: malformed setting string '" << command << "'" << endl;
    			continue;
    		}	
    		cout << "Command: " << command << endl;
    	}
    	string
    		output_file_name;
    	if( !settings.get( "output-file-name", &output_file_name ) || output_file_name.empty( ) )
    		cerr << "Error: Invalid format for 'output-file-name' (expected text)" << endl;
    	else
    		cout << "output-file-name = " << output_file_name << endl;
    	int
    		sub_period;
    	if( !settings.get( "sub-period", &sub_period ) )
    		cerr << "Error: Invalid format for 'sub-period' (expected integer)" << endl;
    	else
    		cout << "sub-period = " << sub_period << endl;		
    	double
    		factor;
    	if( !settings.get( "factor", &factor ) )
    		cerr << "Error: Invalid format for 'factor' (expected floating-point)" << endl;
    	else
    		cout << "factor = " << factor << endl;		
    	return 0;	
    }
    Personally, though, I would go with an existing library, if possible.
    Last edited by Sebastiani; 06-19-2009 at 10:40 PM. Reason: formatting
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  7. #7
    Kiss the monkey. CodeMonkey's Avatar
    Join Date
    Sep 2001
    Posts
    937
    All much-appreciated suggestions. Thanks alot. I'm partial to boost, but then again having it built on every target system becomes a real pain. Thanks for the proposed "do-it-yourself" methods.
    "If you tell the truth, you don't have to remember anything"
    -Mark Twain

  8. #8
    Kiss the monkey. CodeMonkey's Avatar
    Join Date
    Sep 2001
    Posts
    937
    I'd like to use Sebastiani's method. It seems to fit my needs most closely. I haven't put this into context totally yet, but does anything about the below code scream ugly?
    Code:
    //
    //		Example invocation of the program:
    // 
    // $./cavity_gcc image.bmp -TEmode -!check -n=5000 -multirun_frequency -dt=0.24 ../out/*DATE*
    //
    //
    //
    
    namespace settings
    {
    	void set_options(int argc, char* argv[]);
    	int read_commands(int argc, char* argv[], unsigned int begin_index);
    
    	//User-specific variables and flags
    
    	long double dt = 0.001;
    	int load_mode = -1;
    	unsigned int mode = 0;
    	unsigned int nthreads = boost::hardware_concurrency();
    	unsigned int sub_period = 5;
    	unsigned int norm_period = 5*5;
    	unsigned int n = 0;
            std::string mrindex;
    	bool TEmode = false;   bool freq = true;
    	bool TMmode = true;    bool check = true;
    	bool TEbound = true;   bool extended_check = true;
    	bool boundary = true;
    }
    
    template<class T>
    bool get_setting( const std::string & name, T & value )
    {
    	using namespace settings;
    	auto iter = usr_opt.find(name);
    	if( iter == usr_opt.end() )
    		return false;
    	std::istringstream iss( iter->second );
    	return iss >> std::boolalpha >> value;
    }
    
    void settings::set_options(int argc, char* argv[])
    {
    	if(argc < 2)
    		throw no_input_file();
    	paths["bitmap"] = argv[1];
    	
    	int out_index = read_commands(argc, argv, 2);
    	create_output_dir( out_index == -1 ? std::string() : argv[out_index] );
    	
    	#define SET_VARIABLE( x ) get_setting( (#x) , (x) )
    	SET_VARIABLE( n );           SET_VARIABLE( dt );                 SET_VARIABLE( sub_period );
    	SET_VARIABLE( norm_period ); SET_VARIABLE( nthreads );           SET_VARIABLE( freq );
    	SET_VARIABLE( check );       SET_VARIABLE( extended_check );     SET_VARIABLE( loadmode );
    	SET_VARIABLE( mrindex );     SET_VARIABLE( multirun_frequency ); SET_VARIABLE( TEmode );
    	SET_VARIABLE( boundary );    SET_VARIABLE( TEbound );            SET_VARIABLE( TMmode );
    	SET_VARIABLE( mode );
            #undef SET_VARIABLE	
    }
    
    int settings::read_commands(int argc, char* argv[], unsigned int begin_index) //returns index of output directory or -1
    {
    	for(unsigned int i = begin_index; i < argc; ++i)
    	{
    		std::string piece = argv[i];
    		try {
    			if( piece[0] != '-' )
    				throw no_setting();
    
    			if( piece[1] == '!' && piece.size() > 2)
    				usr_opt[ piece.substr(2) ] = "false";
    			else if( piece.find('=') != std::string::npos )
    			{
    				piece.erase(piece.begin()); //no more '-'
    				std::istringstream iss(piece);
    				std::string name, value;
    				std::getline( iss, name, '=' );
    				iss >> value;
    				if( name.empty() || value.empty() )
    					throw bad_setting();
    				usr_opt[ name ] = value;
    			}
    			else
    				usr_opt[ piece.substr(2) ] = "true";
    		}//try
    		catch( bad_setting )
    		{ std::cerr << "Bad setting: " << piece << std::endl; }
    		catch( no_setting )
    		{
    			if(i < argc-1)
    				std::cerr << "Bad setting: " << piece << std::endl;
    			else
    				return i;
    		}
    	}//for
    
    return -1;
    }
    *edit*
    The only trouble is that I have, for brevity, forgone the ability to warn the user if a setting is nonsense. If the user misspells something, the program will not notice, and the intended variable will default. I'm okay with this, since I'm one of the few people using the program. Still, I wonder how dangerous it could be.
    Last edited by CodeMonkey; 06-21-2009 at 02:12 AM. Reason: added stuff
    "If you tell the truth, you don't have to remember anything"
    -Mark Twain

  9. #9
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    I haven't gone through the entire thing just yet, but you could probably do away with the default value ("false"), leaving the string empty, and returning false from get_setting, if it is.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  10. #10
    Student legit's Avatar
    Join Date
    Aug 2008
    Location
    UK -> Newcastle
    Posts
    156
    Quote Originally Posted by CodeMonkey View Post
    I'd like to use Sebastiani's method. It seems to fit my needs most closely. I haven't put this into context totally yet, but does anything about the below code scream ugly?
    Code:
    ...
    int settings::read_commands(int argc, char* argv[], unsigned int begin_index) //returns index of output directory or -1
    {
    	for(unsigned int i = begin_index; i < argc; ++i)
    	{
    		std::string piece = argv[i];
    		try {
    			if( piece[0] != '-' )
    				throw no_setting();
    
    			if( piece[1] == '!' && piece.size() > 2)
    				usr_opt[ piece.substr(2) ] = "false";
    			else if( piece.find('=') != std::string::npos )
    			{
    				piece.erase(piece.begin()); //no more '-'
    				std::istringstream iss(piece);
    				std::string name, value;
    				std::getline( iss, name, '=' );
    				iss >> value;
    				if( name.empty() || value.empty() )
    					throw bad_setting();
    				usr_opt[ name ] = value;
    			}
    			else
    				usr_opt[ piece.substr(2) ] = "true";
    		}//try
    		catch( bad_setting )
    		{ std::cerr << "Bad setting: " << piece << std::endl; }
    		catch( no_setting )
    		{
    			if(i < argc-1)
    				std::cerr << "Bad setting: " << piece << std::endl;
    			else
    				return i;
    		}
    	}//for
    
    return -1;
    }
    Your indentation :P

  11. #11
    Kiss the monkey. CodeMonkey's Avatar
    Join Date
    Sep 2001
    Posts
    937
    The indentation seems logical to me. What would you prefer, Mr. Legitimate?
    "If you tell the truth, you don't have to remember anything"
    -Mark Twain

  12. #12
    Student legit's Avatar
    Join Date
    Aug 2008
    Location
    UK -> Newcastle
    Posts
    156
    Notice the smiley after my statement, I was joking :S

  13. #13
    Kiss the monkey. CodeMonkey's Avatar
    Join Date
    Sep 2001
    Posts
    937
    Yes, I understand

    Forgive me, Sebastiani, but I don't understand your comment. Do what "false" default are you referring?

    *edit* looks like I have to use '~' for "not" rather than '!', since '!' is reserved by bash.
    Last edited by CodeMonkey; 06-21-2009 at 09:52 PM.
    "If you tell the truth, you don't have to remember anything"
    -Mark Twain

  14. #14
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,057
    Well, '~' is typically expanded by bash as well to mean your home directory. If you put it in front of a word, say "~this", it won't be expanded unless there's a user called "this", in which case it will be expanded to this's home directory.

    You could just use a "--no-" prefix like e.g. gcc does for warnings.

    Have fun.
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 4
    Last Post: 04-03-2008, 09:07 PM
  2. Add/Delete Remotely a user
    By Scarvenger in forum Windows Programming
    Replies: 5
    Last Post: 03-24-2008, 08:36 AM
  3. ~ User Input script help~
    By indy in forum C Programming
    Replies: 4
    Last Post: 12-02-2003, 06:01 AM
  4. Beginner question- user input termination
    By westm2000 in forum C Programming
    Replies: 3
    Last Post: 12-02-2001, 02:48 PM
  5. Stopping a user from typeing.
    By knave in forum C++ Programming
    Replies: 4
    Last Post: 09-10-2001, 12:21 PM