void_t "can implement concepts" ?
void_t "can implement concepts"?
我正在观看Walter Brown关于模板元编程的CppCon2014演讲的第二部分,在此期间他讨论了他的小说void_t<>
构造的用途。在他的演讲中,彼得·索默拉德问了他一个我不太明白的问题。(链接直接指向问题,正在讨论的代码直接发生在之前(
索默拉德问道
Walter,这是否意味着我们现在实际上可以实现概念精简版?
沃尔特回应
哦,是的!我做到了... 它没有完全相同的语法。
我理解这种交流是关于概念精简版的。这种模式真的那么通用吗?无论出于何种原因,我都没有看到它。有人可以解释(或草图(这样的东西是什么样子的吗?这仅仅是关于enable_if
和定义特征,还是提问者指的是什么?
void_t
模板定义如下:
template<class ...> using void_t = void;
然后,他使用它来检测类型语句的格式是否正确,并使用它来实现is_copy_assignable
类型特征:
//helper type
template<class T>
using copy_assignment_t
= decltype(declval<T&>() = declval<T const&>());
//base case template
template<class T, class=void>
struct is_copy_assignable : std::false_type {};
//SFINAE version only for types where copy_assignment_t<T> is well-formed.
template<class T>
struct is_copy_assignable<T, void_t<copy_assignment_t<T>>>
: std::is_same<copy_assignment_t<T>,T&> {};
由于演讲,我了解了这个例子是如何工作的,但我不明白我们如何从这里到像概念精简版这样的东西。
是的,概念精简版基本上是装扮SFINAE。此外,它还允许更深入的内省,以实现更好的重载。但是,这仅在概念谓词定义为concept bool
时才有效。改进的重载不适用于当前概念谓词,但可以使用条件重载。让我们看看如何在 C++14 中定义谓词、约束模板和重载函数。这有点长,但它介绍了如何在 C++14 中创建实现这一目标所需的所有工具。
定义谓词
首先,到处阅读带有所有std::declval
和decltype
的谓词有点丑陋。相反,我们可以利用这样一个事实,即我们可以使用尾随 decltype(来自 Eric Niebler 的博客文章(约束函数,如下所示:
struct Incrementable
{
template<class T>
auto requires_(T&& x) -> decltype(++x);
};
因此,如果++x
无效,则 requires_
成员函数不可调用。因此,我们可以创建一个 models
特征,该特征仅使用 void_t
检查requires_
是否可调用:
template<class Concept, class Enable=void>
struct models
: std::false_type
{};
template<class Concept, class... Ts>
struct models<Concept(Ts...), void_t<
decltype(std::declval<Concept>().requires_(std::declval<Ts>()...))
>>
: std::true_type
{};
约束模板
所以当我们想根据概念约束模板时,我们仍然需要使用 enable_if
,但我们可以使用这个宏来帮助使其更干净:
#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0
因此,我们可以定义一个基于Incrementable
概念约束的increment
函数:
template<class T, REQUIRES(models<Incrementable(T)>())>
void increment(T& x)
{
++x;
}
因此,如果我们用不Incrementable
的东西调用increment
,我们将得到这样的错误:
test.cpp:23:5: error: no matching function for call to 'incrementable'
incrementable(f);
^~~~~~~~~~~~~
test.cpp:11:19: note: candidate template ignored: disabled by 'enable_if' [with T = foo]
template<class T, REQUIRES(models<Incrementable(T)>())>
^
重载函数
现在,如果我们想做重载,我们想使用条件重载。假设我们想使用概念谓词创建一个std::advance
,我们可以这样定义它(现在我们将忽略可递减的情况(:
struct Incrementable
{
template<class T>
auto requires_(T&& x) -> decltype(++x);
};
struct Advanceable
{
template<class T, class I>
auto requires_(T&& x, I&& i) -> decltype(x += i);
};
template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())>
void advance(Iterator& it, int n)
{
it += n;
}
template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())>
void advance(Iterator& it, int n)
{
while (n--) ++it;
}
但是,这会导致模棱两可的重载(在概念精简版中,这仍然是一个模棱两可的重载,除非我们将谓词更改为引用concept bool
中的其他谓词(当它与迭代器一起使用时std::vector
。我们要做的是排序调用,我们可以使用条件重载来完成。可以考虑写这样的东西(C++无效(:
template<class Iterator>
void advance(Iterator& it, int n) if (models<Advanceable(Iterator, int)>())
{
it += n;
}
else if (models<Incrementable(Iterator)>())
{
while (n--) ++it;
}
因此,如果未调用第一个函数,它将调用下一个函数。因此,让我们从两个函数实现它开始。我们将创建一个名为 basic_conditional
的类,它接受两个函数对象作为模板参数:
struct Callable
{
template<class F, class... Ts>
auto requires_(F&& f, Ts&&... xs) -> decltype(
f(std::forward<Ts>(xs)...)
);
};
template<class F1, class F2>
struct basic_conditional
{
// We don't need to use a requires clause here because the trailing
// `decltype` will constrain the template for us.
template<class... Ts>
auto operator()(Ts&&... xs) -> decltype(F1()(std::forward<Ts>(xs)...))
{
return F1()(std::forward<Ts>(xs)...);
}
// Here we add a requires clause to make this function callable only if
// `F1` is not callable.
template<class... Ts, REQUIRES(!models<Callable(F1, Ts&&...)>())>
auto operator()(Ts&&... xs) -> decltype(F2()(std::forward<Ts>(xs)...))
{
return F2()(std::forward<Ts>(xs)...);
}
};
所以现在这意味着我们需要将我们的函数定义为函数对象:
struct advance_advanceable
{
template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())>
void operator()(Iterator& it, int n) const
{
it += n;
}
};
struct advance_incrementable
{
template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())>
void operator()(Iterator& it, int n) const
{
while (n--) ++it;
}
};
static conditional<advance_advanceable, advance_incrementable> advance = {};
所以现在如果我们尝试将其与std::vector
一起使用:
std::vector<int> v = { 1, 2, 3, 4, 5, 6 };
auto iterator = v.begin();
advance(iterator, 4);
std::cout << *iterator << std::endl;
它将编译并打印出5
。
但是,std::advance
实际上有三个重载,因此我们可以使用该basic_conditional
来实现conditional
,该适用于使用递归的任意数量的函数:
template<class F, class... Fs>
struct conditional : basic_conditional<F, conditional<Fs...>>
{};
template<class F>
struct conditional<F> : F
{};
所以,现在我们可以像这样编写完整的std::advance
:
struct Incrementable
{
template<class T>
auto requires_(T&& x) -> decltype(++x);
};
struct Decrementable
{
template<class T>
auto requires_(T&& x) -> decltype(--x);
};
struct Advanceable
{
template<class T, class I>
auto requires_(T&& x, I&& i) -> decltype(x += i);
};
struct advance_advanceable
{
template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())>
void operator()(Iterator& it, int n) const
{
it += n;
}
};
struct advance_decrementable
{
template<class Iterator, REQUIRES(models<Decrementable(Iterator)>())>
void operator()(Iterator& it, int n) const
{
if (n > 0) while (n--) ++it;
else
{
n *= -1;
while (n--) --it;
}
}
};
struct advance_incrementable
{
template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())>
void operator()(Iterator& it, int n) const
{
while (n--) ++it;
}
};
static conditional<advance_advanceable, advance_decrementable, advance_incrementable> advance = {};
使用λ重载
但是,此外,我们可以使用 lambda 来编写它,而不是函数对象,这有助于使其编写起来更清晰。所以我们使用这个STATIC_LAMBDA
宏在编译时构造 lambda:
struct wrapper_factor
{
template<class F>
constexpr wrapper<F> operator += (F*)
{
return {};
}
};
struct addr_add
{
template<class T>
friend typename std::remove_reference<T>::type *operator+(addr_add, T &&t)
{
return &t;
}
};
#define STATIC_LAMBDA wrapper_factor() += true ? nullptr : addr_add() + []
并添加一个constexpr
的make_conditional
函数:
template<class... Fs>
constexpr conditional<Fs...> make_conditional(Fs...)
{
return {};
}
那么我们现在可以像这样编写advance
函数:
constexpr const advance = make_conditional(
STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Advanceable(decltype(it), int)>()))
{
it += n;
},
STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Decrementable(decltype(it))>()))
{
if (n > 0) while (n--) ++it;
else
{
n *= -1;
while (n--) --it;
}
},
STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Incrementable(decltype(it))>()))
{
while (n--) ++it;
}
);
这比使用函数对象版本更紧凑和可读。
此外,我们可以定义一个modeled
函数来减少丑陋decltype
:
template<class Concept, class... Ts>
constexpr auto modeled(Ts&&...)
{
return models<Concept(Ts...)>();
}
constexpr const advance = make_conditional(
STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Advanceable>(it, n)))
{
it += n;
},
STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Decrementable>(it)))
{
if (n > 0) while (n--) ++it;
else
{
n *= -1;
while (n--) --it;
}
},
STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Incrementable>(it)))
{
while (n--) ++it;
}
);
最后,如果您有兴趣使用现有的库解决方案(而不是像我展示的那样滚动自己的解决方案(。有一个 Tick 库,它提供了一个用于定义概念和约束模板的框架。Fit 库可以处理函数和重载。
- C++20 concepts: int not swappable_with int
- <concepts> 使用 GCC 试用标准库
- Leetcode 28 - Implement strStr(): question
- 为什么在std::overload而不是concepts/constexpr如果在std::visit中?
- C++ concepts and std::cout
- "Has a" C++关系,最佳做法是让一个类"implement"多个抽象基类?
- SIMD: implement _mm256_max_epu64_ and _mm256_min_epu64_
- To implement FlannBasedMatcher
- Eclipse "implement method"函数不适用于模板类
- void_t "can implement concepts" ?
- Implement strstr
- 在concepts TS中有任何预定义的概念吗?
- c++ Concepts TS会启用多个参数包吗?
- C++ "Concepts"与ObjectiveC "protocols"和Java "Interfaces"相同吗?
- implement crank-nicolson in c++
- static assert - C++ concepts vs static_assert
- Implement pow(x, n)
- 与汽车发电机相关的助推精神业力和助推变体"concepts"