Search code examples
c++templates

How to write a templated function that accepts a std::optional or an arithmetic type and selects the correct underlying type


I am writing a templated function that can take a std::optional or a arithmetic type as the argument. The function then needs to select the underlying type and convert a string to the underlying arithmetic type or the arithmetic type.


#include <optional>
#include <cstdint>
#include <string>
#include <type_traits>

template<typename T, typename F = std::conditional_t<std::is_arithmetic_v<T>,
                                                       T,
                                                       typename T::value_type>>
void convertNumericColumn(std::string &column, T &value)
{
  if (!column.empty())
  {
    try
    {
//      value = boost::lexical_cast<F>(column); // Only commented out to ensure the code compiles.
    }
    catch(...){}
  };
}

int  main(int, char **)
{
  std::string value = "123";

  std::optional<std::uint64_t> test1;
  std::uint64_t test2;

  convertNumericColumn(value, test1); // Compile OK
  convertNumericColumn(value, test2); // Does not compile.
}

https://godbolt.org/z/9vfsK1oqr

The errors are error: no matching function for call to 'convertNumericColumn(std::string&, uint64_t&)' and error: 'long unsigned int' is not a class, struct, or union type


Solution

  • The problem is that you are using std::conditional_t and passing T which doesn't have value_type i.e. long unsigned int doesn't have value_type, so the compilation fails. T must have a value_type as per your code else it fails with the error you get.

    Instead you can use if constexpr like this:

    #include <optional>
    #include <cstdint>
    #include <string>
    #include <type_traits>
    #include <iostream>
    #include <boost/lexical_cast.hpp>
    
    template<typename T>
    void convertNumericColumn(std::string &column, T &value)
    {
        if (!column.empty())
        {
            try
            {
                if constexpr (std::is_arithmetic_v<T>)
                {
                    value = boost::lexical_cast<T>(column);    
                }
                else
                {
                    value = boost::lexical_cast<typename T::value_type>(column);
                }
            }
            catch(...){}
        };
    }
    int  main(int, char **)
    {
      std::string value = "123";
    
      std::optional<std::uint64_t> test1;
      std::uint64_t test2;
    
      convertNumericColumn(value, test1);
      convertNumericColumn(value, test2);
      std::cout << test1.value() << ' ' << test2 << '\n';
    }