0

I have implemented a simple class for reading values from command line arguments. It looks like this:

#pragma once
#include <string>
#include <cstring>
#include <stdexcept>

//! Reads key value pairs, or just the flag for booleans
class CommandLine
{
public:
  //! Use the ignore argument for ignoring first N command line arguments, useful if the first argument is for example a file name
  CommandLine(int argc, const char** argv, int ignore = 1) : _argc(argc), _argv(argv), _ignore(ignore) {}

  //! Finds a command line parameter using given string. The param is searched as is, so if your params begin with -, you need to call this method with "-myParam"
  //! The found param will be set to true if value was found and false otherwise
  //! Parse errors will not throw, instead you will get a default value and found will be false
  template <typename T>
  T getValue(const std::string& flagName, T defaultValue, bool& found);

  //! Shorthand if you don't care if value is found
  template <typename T>
  T getValue(const std::string& flagName, T defaultValue) { bool unused; return getValue(flagName, defaultValue, unused); }

  //! Parses value at index, this is absolute index from 0, the ignore parameter in constructor does not affect this
  //! Otherwise behaves as the std::string overload
  template <typename T>
  T getValue(int offset, T defaultValue, bool& found);

  //! Shorthand if you don't care if value is found
  template <typename T>
  T getValue(int offset, T defaultValue) { bool unused; return getValue(offset, defaultValue, unused); }

private:
  int _argc;
  const char** _argv;
  int _ignore = 0;

  int findFlag(const std::string& name) const;
};

template <>
int CommandLine::getValue(int offset, int defaultValue, bool& found)
{
  try
  {
    int res = std::stoi(_argv[offset]);
    found = true;
    return res;
  }
  catch (std::invalid_argument const&) {}
  catch (std::out_of_range const&) {}
  found = false;
  return defaultValue;
}

template <>
bool CommandLine::getValue(const std::string& flagName, bool defaultValue, bool& found)
{
  return findFlag(flagName) != -1;
}

template<typename T>
inline T CommandLine::getValue(const std::string& flagName, T defaultValue, bool& found)
{
  int i = findFlag(flagName);
  if (i > -1 && i+1 < _argc)
  {
    return getValue(i + 1, defaultValue, found);
  }
  else
  {
    found = false;
    return defaultValue;
  }
}

template <typename T>
T CommandLine::getValue(int offset, T defaultValue, bool& found)
{
  static_assert(false, "Parsing of this type is not supported.");
}

int CommandLine::findFlag(const std::string& name) const
{
  for (int i = _ignore; i < _argc; ++i)
  {
    if (name == _argv[i])
    {
      return i;
    }
  }
  return -1;
}

You can see that what I did is that I have template specialization for int for the method getValue(int offset, T defaultValue, bool& found), and I can later add others like double. My intent was to throw a readable error if one tries to parse value that does not have a parser implemented, so I added a generic implementation that should only be used if no specialization is available. From code above:

template <typename T>
T CommandLine::getValue(int offset, T defaultValue, bool& found)
{
  static_assert(false, "Parsing of this type is not supported.");
}

I assumed this code would be only attempted to be compiled if you try to use it. This assumption seems correct for MSVC. But when I tried it on linux with g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0, I get an error:

myname@linuxpc:~/programming/project/build$ g++ -std=c++17 -c ../src/main.cpp
In file included from ../src/main.cpp:5:
../src/helpers/CommandLine.h: In member function ‘T CommandLine::getValue(int, T, bool&)’:
../src/helpers/CommandLine.h:70:17: error: static assertion failed: Parsing of this type is not supported.
   70 |   static_assert(false, "Parsing of this type is not supported.");
      |                 ^~~~~
                ^~~~~

I am thinking it is more likely that I am doing this wrong.

How to implement a class that has explicit template specializations for each type and throws a compile error if nonexistent specialization is attempted to be called?

0 Answers0