通常当您编写接受参数的CLI工具时,您必须处理它们。大多数情况下,您希望根据参数的值在行为之间切换。
以下是一个常见用例,程序接受一个类型,然后根据该类型打印一些内容。我正在使用Boost预处理并自动生成整个if-else
分支。
这在可维护性方面非常好,因为我只需要在引入新类型时更新define
。另一方面,它远非现代和优雅。
我考虑使用better-enums
来避免使用if-else
使用_from_string实用程序函数将字符串转换为枚举。但是,从枚举到类型的方式对我来说仍然模糊不清。
有关如何保持当前实现的良好可维护性但避免使用预处理器和宏功能的任何建议吗?
#include <iostream>
#include <cstdlib>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <type_traits>
using a_type = int;
using b_type = long;
using c_type = float;
using d_type = double;
#define TYPES (a)(b)(c)(d)
template<typename T>
void foo(){
T num = 1;
std::cout << typeid(decltype(num)).name() << " : "<< num << std::endl;
};
int main(int argc, char **argv)
{
if (argc < 1) {
return 1;
}
std::string type = argv[1];
if (false) {
#define LOOP_BODY(R, DATA, T) \
} \
else if (type == BOOST_PP_STRINGIZE(T)) { \
foo<BOOST_PP_CAT(T, _type)>(); \
BOOST_PP_SEQ_FOR_EACH(LOOP_BODY, _, TYPES);
#undef LOOP_BODY
} else {
std::cout << "ERROR: Unknown type " << type << std::endl;
}
}
的工作示例
答案 0 :(得分:3)
另一种方法是使用普通数组而不是std::find_if
代替if-else
:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <string>
#include <typeinfo>
struct Handler {
char const* name;
void(*fn)(std::string const&); // Or std::function<> to accept lambdas.
};
struct A {};
struct B {};
template<class T>
void foo(std::string const& name) {
std::cout << "foo<" << typeid(T).name() << ">: " << name << '\n';
}
int main(int, char** av) {
Handler const handlers[] = {
{"a", foo<A>}
, {"b", foo<B>}
};
std::string const name = av[1];
auto handler = std::find_if(std::begin(handlers), std::end(handlers), [&name](auto const& h) {
return name == h.name;
});
if(handler != std::end(handlers))
handler->fn(name);
}
答案 1 :(得分:1)
您不需要使用预处理器来存储任意类型的列表并为它们生成代码。我们可以使用可变参数模板和编译时字符串。您可以将预处理器使用与生成的名称和类型对隔离开来。
首先,让我们为编译时字符序列定义一个包装器。请注意,_cs
文字的使用是非标准的,但在每个主要编译器中都可用,并且可能是C ++ 20的一部分:
template <char... Cs>
using ct_str = std::integer_sequence<char, Cs...>;
template <typename T, T... Cs>
constexpr ct_str<Cs...> operator""_cs() { return {}; }
然后我们可以定义一个存储一对名称和类型的空类型:
template <typename Name, typename T>
struct named_type
{
using name = Name;
using type = T;
};
一个宏可以方便地实例化它:
#define NAMED_TYPE(type) \
named_type<decltype(#type ## _cs), type>
您现在可以使用空的可变参数模板类来存储您的类型:
template <typename... Ts>
struct named_type_list { };
using my_types = named_type_list<
NAMED_TYPE(int),
NAMED_TYPE(long),
NAMED_TYPE(float),
NAMED_TYPE(double)
>;
现在,让我们看看main
应该如何看待:
int main()
{
const std::string input{"float"};
handle(my_types{}, input, [](auto t)
{
print(typename decltype(t)::name{});
});
}
以上内容将打印出"float"
。我们可以通过解压缩handle
类型列表并使用 fold表达式来查找匹配的类型名称来实现named_type
:
template <typename... Ts, typename F>
void handle(named_type_list<Ts...>, const std::string& input, F&& f)
{
( (same(input, typename Ts::name{}) && (f(Ts{}), true) ) || ...);
}
检查std::string
和ct_str
之间的相等性是令人讨厌的,但可行:
template <std::size_t... Is, char... Cs>
bool same_impl(const std::string& s,
std::integer_sequence<char, Cs...>,
std::index_sequence<Is...>)
{
return ((s[Is] == Cs) && ...);
}
template <char... Cs>
bool same(const std::string& s, std::integer_sequence<char, Cs...> seq)
{
return s.size() >= sizeof...(Cs)
&& same_impl(s, seq, std::make_index_sequence<sizeof...(Cs)>{});
}
final result live on wandbox.org
请注意,此答案使用C ++ 17 fold expressions。您可以使用以下技术之一在C ++ 14中替换它们:
递归可变参数模板函数,其中基本案例返回默认累积值,递归案例在尾部和头部之间执行操作。
C ++ 11包扩展技巧,例如for_each_argument
。
调度发生短路:
( (same(input, typename Ts::name{}) && (f(Ts{}), true) ) || ...);
由于f
表达式和, true
运算符,此折叠表达式将在||
的第一次调用时停止。