【问题标题】:How do I define operator >> (istream &, ...) for a standard type that lacks a pre-defined operator without extending namespace `std`?如何为缺少预定义运算符而不扩展命名空间`std`的标准类型定义运算符>>(istream&,...)?
【发布时间】:2020-03-14 09:24:44
【问题描述】:

显然,将 (almost) 任何内容添加到 std 命名空间是未定义行为。

我正在使用没有 std::chrono::parse()(仅限 C++20)的 C++14,但我需要从 istream 反序列化类型为 std::chrono::millisecondsstd::chrono::duration 的特化)的值。

虽然这可行,但我找不到任何允许它不是 UB 的例外:

namespace std {
std::istream& operator >>(std::istream & is, std::chrono::milliseconds & ms) {
    std::string s;
    is >> s;
    ms = std::chrono::milliseconds(std::stoi(s));
    return is;
}
}

由于这两种参数类型都不是 my 类型,我不确定如何在 std 命名空间之外安全地定义此运算符。

请注意,运算符将在 Boost::program_options 的深处调用,因此我认为我不能在自己的命名空间中定义运算符然后使用 using my_ns::operator>>,因为 using 声明的范围不会扩展到program_options 范围。

我是怎么来到这里的

作为Boost::program_options 的用户,我有一个特定的配置变量,我从存储为std::chrono::milliseconds 值的配置文件中读取:

std::chrono::milliseconds period;
po::options_description config_only_opts;
config_only_opts.add_options()
    ("control.period", po::value<std::chrono::milliseconds>(&period), "Specify the period in milliseconds");

// ...

auto istream = ifstream("config.cfg");
po::variables_map vm;
po::store(po::parse_config_file(istream, config_file_opts, false), vm);

根据Boost::program_options 文档,对于定义了operator &gt;&gt; (istream &amp;, ...) 函数的类型,可以从配置文件中反序列化值。

如果没有上面提到的std的扩展,我最终会出现这种编译器错误:

/home/david/opt/boost-1.69/include/boost/lexical_cast/detail/converter_lexical.hpp: In instantiation of ‘struct boost::detail::deduce_target_char_impl<boost::detail::deduce_character_type_later<std::chrono::duration<long int, std::ratio<1, 1000> > > >’:
/home/david/opt/boost-1.69/include/boost/lexical_cast/detail/converter_lexical.hpp:270:89:   required from ‘struct boost::detail::deduce_target_char<std::chrono::duration<long int, std::ratio<1, 1000> > >’
/home/david/opt/boost-1.69/include/boost/lexical_cast/detail/converter_lexical.hpp:407:92:   required from ‘struct boost::detail::lexical_cast_stream_traits<std::__cxx11::basic_string<char>, std::chrono::duration<long int, std::ratio<1, 1000> > >’
/home/david/opt/boost-1.69/include/boost/lexical_cast/detail/converter_lexical.hpp:468:15:   required from ‘struct boost::detail::lexical_converter_impl<std::chrono::duration<long int, std::ratio<1, 1000> >, std::__cxx11::basic_string<char> >’
/home/david/opt/boost-1.69/include/boost/lexical_cast/try_lexical_convert.hpp:201:44:   required from ‘bool boost::conversion::detail::try_lexical_convert(const Source&, Target&) [with Target = std::chrono::duration<long int, std::ratio<1, 1000> >; Source = std::__cxx11::basic_string<char>]’
/home/david/opt/boost-1.69/include/boost/lexical_cast.hpp:41:60:   required from ‘Target boost::lexical_cast(const Source&) [with Target = std::chrono::duration<long int, std::ratio<1, 1000> >; Source = std::__cxx11::basic_string<char>]’
/home/david/opt/boost-1.69/include/boost/program_options/detail/value_semantic.hpp:92:36:   required from ‘void boost::program_options::validate(boost::any&, const std::vector<std::__cxx11::basic_string<charT> >&, T*, long int) [with T = std::chrono::duration<long int, std::ratio<1, 1000> >; charT = char]’
/home/david/opt/boost-1.69/include/boost/program_options/detail/value_semantic.hpp:184:21:   required from ‘void boost::program_options::typed_value<T, charT>::xparse(boost::any&, const std::vector<std::__cxx11::basic_string<charT> >&) const [with T = std::chrono::duration<long int, std::ratio<1, 1000> >; charT = char]’
/home/david/myproj/Config.cpp:208:1:   required from here
/home/david/opt/boost-1.69/include/boost/lexical_cast/detail/converter_lexical.hpp:243:13: error: static assertion failed: Target type is neither std::istream`able nor std::wistream`able
             BOOST_STATIC_ASSERT_MSG((result_t::value || boost::has_right_shift<std::basic_istream<wchar_t>, T >::value),
             ^

【问题讨论】:

  • operator &gt;&gt; 通常不需要位于 std 命名空间或任何其他命名空间中。如果您可以添加一些关于您如何在这个职位上结束的详细信息,我们也许可以让您摆脱它。
  • @user4581301 我已按要求添加了一些背景 - 谢谢。
  • 我认为这个问题主要是因为我想将结果存储std::chrono::milliseconds 中,而不是序列化格式的任何特殊之处。也许我应该通过纯整数提取值,然后在解析完成后使用它来初始化 period 值。

标签: c++ namespaces deserialization chrono boost-program-options


【解决方案1】:

即使标准允许运算符重载,我也不建议添加该函数。这不是从std::istream 读取std::chrono::milliseconds 的通用方法。您所拥有的是您的应用程序自己从std::istream 读取此类对象的方式。

我建议在您的应用程序自己的namespace 中添加一个函数。

namespace MyApp
{
   std::istream& readChronoMilliSeconds(std::istream& is, std::chrono::milliseconds & ms)
   {
      std::string s;
      is >> s;
      ms = std::chrono::milliseconds(std::stoi(s));
      return is;
   }
}

【讨论】:

  • 我可以看到这样的函数有多么有用,但不幸的是,Boost::program_options 需要 operator&gt;&gt; 用于它反序列化的类型。您认为我只是将配置选项加载为纯整数会更好吗?似乎很不幸,因为我有其他从配置文件加载的自定义类型(枚举、结构等),但它们是我自己的类型,所以我可以轻松地为它们定义 operator&gt;&gt;(istream, ...)。也许我应该用我自己的自定义类包装std::chrono::milliseconds,然后将其用于反序列化?
  • @davidA,最后一句话对我来说很有意义。
【解决方案2】:

对输入参数使用花哨的类型是自欺欺人,因为输入参数通常只是数字、字符串或它们的列表。

我建议采用适当的整数值并在内部转换为毫秒。

int period_in_ms = 0;
...
config_only_opts.add_options()
    ("control.period_in_ms", po::value<int>(&period_in_ms), "Specify the period in milliseconds");
...
auto period = std::chrono::milliseconds(period_in_ms);

您可以使用 Boost.ProgramOptions 中的“通知”机制来就地更新period 的值,但这并不值得。

如果您想以某种格式输入参数(例如,以“ms”结尾,如“100ms”)然后使用value&lt;std::string&gt; 并进行转换/解析。

【讨论】:

    猜你喜欢
    • 2021-06-11
    • 1970-01-01
    • 2015-04-26
    • 2020-10-10
    • 2021-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-11
    相关资源
    最近更新 更多