为什么不能将概念传递给模板元函数?
Why cannot concepts be passed to template meta-functions?
考虑由 Boost.MP11、Brigand 等库提供的任何常见类型级算法...
例如:
template<typename... Args>
struct TypeList;
using my_types = TypeList<int, float, char, float, double>;
constexpr int count = boost::mp11::mp_count_if<my_types, std::is_floating_point>::value;
// this holds:
static_assert(count == 3);
注意 std::is_floating_point
可以定义为:
template<typename T>
struct is_floating_point { constexpr bool value = __compiler_magic(T); };
同样,我们有 std::floating_point
概念
template<typename T>
concept floating_point = requires (T t) { __other_compiler_magic(T); };
遗憾的是,尽管有相似之处,但似乎没有一种简单的方法可以在不为概念引入手动命名的包装器的情况下编写这样的东西:
constexpr int count = boost::mp11::count_if<my_types, std::floating_point>::value;
我的问题是:为什么此时不能传递概念来代替类型?是缺乏标准化,还是这些库可以通过提供更多重载来解决?
看起来每个概念都必须包装在一个模板化类型中,它只会在其模板参数上调用该概念。
从外面看,概念就像元函数,其域是{类型集} -> bool。编译器能够延迟将参数传递给“传统的”基于类型的元函数,例如 std::is_floating_point
,为什么概念似乎不能发生同样的事情?
字面上的答案是我们有模板模板参数但没有概念模板参数,所以你不能将概念作为模板参数传递。
另一个直截了当的答案是,它从来都不是原始概念提案的一部分,也没有人努力建议将其作为扩展(尽管我已经 collecting use-cases)。
必须回答的一件事是从属概念如何影响包含 - 因为当前概念的使用从不依赖,所以弄清楚包含很简单(实际上,它仍然一点也不简单,但至少所有你需要的东西就在那里)。但是在这样的场景中:
template <template <typename> concept C, typename T>
requires C<T>
void foo(T); // #1
template <typename T>
void foo(T); // #2
可能如果 #1
可行,您想说它是比 #2
更好的候选者,因为它仍然受到限制,而另一个则没有。也许那是微不足道的。但是然后:
template <template <typename> concept C, typename T>
requires C<T>
void bar(T); // #3
template <OtherConcept T>
void bar(T); // #4
假设#3
和#4
都可行,能说哪个更好吗?我们通常说整个过载总是比不同的过载更好——但这里可能不是这种情况。也许这只是模棱两可?
在我看来,这是获得概念模板参数需要回答的主要问题。
另一个问题可能是,我可以写foo<convertible_to<int>>(42)
吗? convertible_to<int>
并不是一个真正的一元概念,但它是一个 类型约束 在某些情况下被视为一个,所以我仍然希望它能起作用。
一旦我们有了这样的东西,我相信 Boost.Mp11 会很快获得类似的东西:
template <template <typename...> concept C>
struct mp_quote_c {
template <typename... T>
using fn = mp_bool<C<T...>>;
};
这样你就可以写:
constexpr int count = mp_count_if_q<my_types, mp_quote_c<std::floating_point>>::value;
考虑由 Boost.MP11、Brigand 等库提供的任何常见类型级算法... 例如:
template<typename... Args>
struct TypeList;
using my_types = TypeList<int, float, char, float, double>;
constexpr int count = boost::mp11::mp_count_if<my_types, std::is_floating_point>::value;
// this holds:
static_assert(count == 3);
注意 std::is_floating_point
可以定义为:
template<typename T>
struct is_floating_point { constexpr bool value = __compiler_magic(T); };
同样,我们有 std::floating_point
概念
template<typename T>
concept floating_point = requires (T t) { __other_compiler_magic(T); };
遗憾的是,尽管有相似之处,但似乎没有一种简单的方法可以在不为概念引入手动命名的包装器的情况下编写这样的东西:
constexpr int count = boost::mp11::count_if<my_types, std::floating_point>::value;
我的问题是:为什么此时不能传递概念来代替类型?是缺乏标准化,还是这些库可以通过提供更多重载来解决?
看起来每个概念都必须包装在一个模板化类型中,它只会在其模板参数上调用该概念。
从外面看,概念就像元函数,其域是{类型集} -> bool。编译器能够延迟将参数传递给“传统的”基于类型的元函数,例如 std::is_floating_point
,为什么概念似乎不能发生同样的事情?
字面上的答案是我们有模板模板参数但没有概念模板参数,所以你不能将概念作为模板参数传递。
另一个直截了当的答案是,它从来都不是原始概念提案的一部分,也没有人努力建议将其作为扩展(尽管我已经 collecting use-cases)。
必须回答的一件事是从属概念如何影响包含 - 因为当前概念的使用从不依赖,所以弄清楚包含很简单(实际上,它仍然一点也不简单,但至少所有你需要的东西就在那里)。但是在这样的场景中:
template <template <typename> concept C, typename T>
requires C<T>
void foo(T); // #1
template <typename T>
void foo(T); // #2
可能如果 #1
可行,您想说它是比 #2
更好的候选者,因为它仍然受到限制,而另一个则没有。也许那是微不足道的。但是然后:
template <template <typename> concept C, typename T>
requires C<T>
void bar(T); // #3
template <OtherConcept T>
void bar(T); // #4
假设#3
和#4
都可行,能说哪个更好吗?我们通常说整个过载总是比不同的过载更好——但这里可能不是这种情况。也许这只是模棱两可?
在我看来,这是获得概念模板参数需要回答的主要问题。
另一个问题可能是,我可以写foo<convertible_to<int>>(42)
吗? convertible_to<int>
并不是一个真正的一元概念,但它是一个 类型约束 在某些情况下被视为一个,所以我仍然希望它能起作用。
一旦我们有了这样的东西,我相信 Boost.Mp11 会很快获得类似的东西:
template <template <typename...> concept C>
struct mp_quote_c {
template <typename... T>
using fn = mp_bool<C<T...>>;
};
这样你就可以写:
constexpr int count = mp_count_if_q<my_types, mp_quote_c<std::floating_point>>::value;