在通用函数中,我使用以下惯用法
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
... other stuff here...
using std::copy;
copy(first, second, d_first);
}
do_something
是一个通用函数,不应该知道任何其他库的特定信息(也许std::
除外)。
现在假设我的命名空间N
中有多个迭代器。
namespace N{
struct itA{using trait = void;};
struct itB{using trait = void;};
struct itC{using trait = void;};
}
我想在此命名空间中重载这些迭代器的副本。 我自然会这样做:
namespace N{
template<class SomeN1, class SomeN2>
SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first){
std::cout << "here" << std::endl;
}
}
但是,当我使用do_something
,N::A
或N::B
作为参数调用N::C
时,即使这些变量与{{ 1}}。
在上述原始功能的背景下,有没有办法赢得N::copy
?
尽管我对模板参数设置了约束,但std::copy
还是首选。
N::copy
但没有帮助。
我还可以尝试其他哪些解决方法来使一般调用复制,而不是namespace N{
template<class SomeN1, class SomeN2, typename = typename SomeN1::trait>
SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first){
std::cout << "here" << std::endl;
}
}
而不是std::copy
命名空间中的副本。
完整代码:
#include<iostream>
#include<algorithm>
namespace N{
struct A{};
struct B{};
struct C{};
}
namespace N{
template<class SomeN1, class SomeN2>
SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first){
std::cout << "here" << std::endl;
}
}
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
using std::copy;
copy(first, second, d_first); // ambiguous call when It is from namespace N (both `std::copy` and `N::copy` could work.
}
int main(){
N::A a1, a2, a3;
do_something(a1, a2, a3);
}
典型的错误消息是
error: call of overloaded ‘copy(N::A&, N::A&, N::A&)’ is ambiguous
我是否正确地认为C ++概念会更喜欢约束而不是约束更少的函数调用?
答案 0 :(得分:4)
您可以在迭代器类中将copy()
声明为public friend function。
这可以代替部分专业化(对于函数而言这是不可能的),因此,由于它们更加专业化,因此它们将被重载解析所首选:
#include <iostream>
#include <algorithm>
#include <vector>
namespace N
{
template<class SomeN1, class SomeN2>
SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first)
{
std::cout << "here" << std::endl;
return d_first;
}
template <class T>
struct ItBase
{
template <class SomeN2>
friend SomeN2 copy(T first, T last, SomeN2 d_first)
{
return N::copy(first, last, d_first);
}
};
struct A : ItBase<A>{};
struct B : ItBase<B>{};
struct C : ItBase<C>{};
}
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
using std::copy;
copy(first, second, d_first);
}
int main(){
N::A a1, a2, a3;
std::cout << "do something in N:" << std::endl;
do_something(a1, a2, a3);
std::vector<int> v = {1,2,3};
std::vector<int> v2(3);
std::cout << "do something in std:" << std::endl;
do_something(std::begin(v), std::end(v), std::begin(v2));
for (int i : v2)
std::cout << i;
std::cout << std::endl;
}
请参见this demo以验证其是否有效。
我引入了一个通用基类,该基类为所有迭代器声明了必要的朋友。因此,不必像声明那样声明标签,只需从ItBase
继承即可。
注意:如果N::copy()
仅应与N
中的这些迭代器一起使用,则可能不再需要它们,因为这些朋友功能无论如何都将在N
中公开显示(好像它们是免费功能)。
在注释中建议,如果N
中的迭代器始终具有公共基类,则只需使用该基类声明N::copy
,例如
namespace N
{
template <class SomeN2>
SomeN2 copy(ItBase first, ItBase last, SomeN2 d_first) { ... }
}
不幸的是,这将产生与期望的相反的效果:std::copy
始终比N::copy
更为可取,因为如果您传递A
的实例,则必须将其抛弃以匹配N::copy
,而std::copy
不需要强制转换。 Here可以看到显然尝试调用std::copy
(这会产生错误,因为N::A
缺少一些typedef)。
因此,您不能利用公共基类来签名N::copy
。我在解决方案中使用一个的唯一原因是避免重复代码(必须在每个迭代器类中声明Friend函数)。我的ItBase
根本不参与重载决议。
但是请注意,如果您的迭代器碰巧要在实现N::copy
中使用的某些公共成员(无论是否从某个公共基类派生而来并不重要),则可以这样做上面的解决方案是这样的:
namespace N
{
template <class T>
struct ItBase
{
template <class SomeN2>
friend SomeN2 copy(T first, T last, SomeN2 d_first)
{
first.some_member();
last.some_member();
return d_first;
}
};
struct A : ItBase<A>{ void some_member() {} };
struct B : ItBase<B>{ void some_member() {} };
struct C : ItBase<C>{ void some_member() {} };
}
请参见here。
同一行上,如果A,B,C具有相同的行为,则可以用以某种方式参数化的通用模板类替换它们。
namespace N
{
template <class T, int I>
struct ItCommon
{
...
};
using A = ItCommon<double,2>;
using B = ItCommon<int, 3>;
using C = ItCommon<char, 5>;
}
...
namespace N{
template<class T, int I, class Other>
SomeN2 copy(ItCommon<T, I> first, ItCommon<T, I> last, Other){
...
}
}
由于(非朋友)copy
函数肯定比std::copy
受约束,并且由于ADL的原因,当其中一个参数属于N
时它将具有较高的优先级命名空间。另外,copy
函数是非朋友,是可选组件。
答案 1 :(得分:3)
一种可能的解决方案是使用另一个函数模板名称和类型区分符,以允许依赖于参数的名称查找在参数的命名空间中找到关联的函数:
template<class T> struct Tag {};
template<class T> Tag<void> tag(T const&);
template<class It1, class It2>
void mycopy(It1 first, It1 second, It2 d_first, Tag<void>) {
std::cout << "std::copy\n";
}
template<class It1, class It2>
void mycopy(It1 first, It1 second, It2 d_first) {
mycopy(first, second, d_first, decltype(tag(first)){}); // Discriminate by the type of It1.
}
namespace N{
struct itA{using trait = void;};
Tag<itA> tag(itA);
template<class It1, class It2>
void mycopy(It1 first, It1 second, It2 d_first, Tag<itA>) {
std::cout << "N::mycopy\n";
}
}
int main() {
char* p = 0;
mycopy(p, p, p); // calls std::copy
N::itA q;
mycopy(q, q, q); // calls N::mycopy
}
答案 2 :(得分:2)
这似乎满足您的要求:
namespace SpecCopy {
template <typename A, typename B, typename C>
void copy(A &&a, B &&b, C &&c) {
std::copy(std::forward<A>(a), std::forward<B>(b), std::forward<C>(c));
}
}
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
using namespace SpecCopy;
copy(first, second, d_first);
}
基本上,它取决于ADL。如果ADL未找到函数,则它将使用SpecCopy::copy
,它是std::copy
的包装。
因此,如果您这样做:
N::A a1, a2, a3;
do_something(a1, a2, a3);
然后do_something
将呼叫N::copy
。
如果您这样做:
std::vector<int> a1, a2;
do_something(a1.begin(), a1.end(), a2.begin());
然后do_something
将呼叫SpecCopy::copy
,后者将呼叫std::copy
。
如果您这样做:
int *a1, *a2, *a3;
do_something(a1, a2, a3);
然后发生与以前相同的事情:do_something
将调用SpecCopy::copy
,后者将调用std::copy
。
答案 3 :(得分:2)
在c ++ 11中,您可以使用标签分发。如果您可以对自定义迭代器进行一些更改,则实现起来会更简单。
#include <iostream>
#include <algorithm>
#include <vector>
#include <type_traits>
// indicates that the type doesn't have a tag type (like pointers and standard iterators)
struct no_tag{};
namespace detail
{
template <typename T>
auto tag_helper(int) -> typename T::tag;
template <typename>
auto tag_helper(long) -> no_tag;
}
// get T::tag or no_tag if T::tag isn't defined.
template <typename T>
using tag_t = decltype(detail::tag_helper<T>(0));
namespace N
{
struct my_iterator_tag {};
struct A{ using tag = my_iterator_tag; };
struct B{ using tag = my_iterator_tag; };
struct C{ using tag = my_iterator_tag; };
}
namespace N
{
template<class SomeN1, class SomeN2>
SomeN2 copy_helper(SomeN1 first, SomeN1 last, SomeN2 d_first, no_tag)
{
std::cout << "calling std::copy\n";
return std::copy(std::forward<SomeN1>(first), std::forward<SomeN1>(last), std::forward<SomeN2>(d_first));
}
template<class SomeN1, class SomeN2>
SomeN2 copy_helper(SomeN1 first, SomeN1 last, SomeN2 d_first, my_iterator_tag)
{
// your custom copy
std::cout << "custom copy function\n";
return {};
}
template<class SomeN1, class SomeN2>
SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first)
{
return copy_helper(std::forward<SomeN1>(first), std::forward<SomeN1>(last), std::forward<SomeN2>(d_first), tag_t<SomeN1>{});
}
}
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first)
{
N::copy(first, second, d_first);
}
int main()
{
N::A a1, a2, a3;
std::cout << "using custom iterator: ";
do_something(a1, a2, a3);
std::cout << "using vector iterator: ";
std::vector<int> v;
do_something(std::begin(v), std::end(v), std::begin(v));
std::cout << "using pointer: ";
int* ptr = new int[10];
do_something(ptr, ptr + 5, ptr);
return 0;
}
首先,我们将自定义迭代器更改为tag
类型(也许更改名称以避免与iterator_category
混淆)。 tag
可以是您想要的任何类型,只需匹配您在copy_helper
中用作标记的类型。
接下来,我们定义一个类型,该类型允许我们访问此tag
类型,如果tag
不存在,则退回到默认类型。这将帮助我们区分自定义迭代器,标准迭代器和指针。我使用的默认类型为no_tag
。 tag_t
通过使用SFINAE和重载分辨率为我们提供了此功能。我们调用函数tag_helper(0)
,它有两个声明。第一个返回T::tag
,第二个返回no_tag
。调用tag_helper(0)
总是会尝试使用第一个版本,因为int
比0
更适合long
。这意味着我们将始终尝试首先访问T::tag
。但是,如果这不可能(未定义T::tag
),则SFINAE会跳入并跳过tag_helper(int)
,选择tag_helper(long)
。
最后,我们只需要为每个标签实现一个复制功能(我称其为copy_helper
),并为实现方便而包装另一个复制功能(我使用了N::copy
)。然后,包装器函数将创建正确的标记类型并调用正确的帮助器函数。
Here是一个实时示例。
如果您稍微移动一下代码,则可以断开命名空间N
并依赖ADL:
#include <iostream>
#include <algorithm>
#include <vector>
#include <type_traits>
// indicates that the type doesn't have a tag type (like pointers and standard iterators)
struct no_tag{};
namespace detail
{
template <typename T>
auto tag_helper(int) -> typename T::tag;
template <typename>
auto tag_helper(long) -> no_tag;
}
// get T::tag or no_tag if T::tag isn't defined.
template <typename T>
using tag_t = decltype(detail::tag_helper<T>(0));
namespace N
{
struct my_iterator_tag {};
struct A{ using tag = my_iterator_tag; };
struct B{ using tag = my_iterator_tag; };
struct C{ using tag = my_iterator_tag; };
template<class SomeN1, class SomeN2>
SomeN2 copy_helper(SomeN1 first, SomeN1 last, SomeN2 d_first, my_iterator_tag)
{
// your custom copy
std::cout << "custom copy function\n";
return {};
}
}
template<class SomeN1, class SomeN2>
SomeN2 copy_helper(SomeN1 first, SomeN1 last, SomeN2 d_first, no_tag)
{
std::cout << "calling std::copy\n";
return std::copy(std::forward<SomeN1>(first), std::forward<SomeN1>(last), std::forward<SomeN2>(d_first));
}
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first)
{
copy_helper(std::forward<It1>(first), std::forward<It1>(second), std::forward<It2>(d_first), tag_t<It1>{});
}
int main()
{
N::A a1, a2, a3;
std::cout << "using custom iterator: ";
do_something(a1, a2, a3);
std::cout << "using vector iterator: ";
std::vector<int> v;
do_something(std::begin(v), std::end(v), std::begin(v));
std::cout << "using pointer: ";
int* ptr = new int[10];
do_something(ptr, ptr + 5, ptr);
return 0;
}
答案 4 :(得分:1)
好,建立在@ paler123上,但不检查现有类型,而是检查It1
是否是指针:
namespace N{
struct A{};
struct B{};
struct C{};
}
namespace N{
template<class SomeN1, class SomeN2>
SomeN2 copy(SomeN1, SomeN1, SomeN2 c){
std::cout << "here" << std::endl;
return c;
}
}
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
if constexpr (std::is_pointer_v<It1>) {
std::copy(first, second, d_first);
}
else
{
copy(first, second, d_first);
}
}
int main(){
N::A a1, a2, a3;
do_something(a1, a2, a3);
int* b1, *b2, *b3;
do_something(b1, b2, b3);
}
仍然是C ++ 17,但是在使用指针的情况下,我们将经过显式的std::copy
,否则,我们将依赖ADL。
通常,您的问题是设计问题。您希望在所有情况下都使用std::copy
,但N
中的对象除外,在这种情况下,您希望ADL可以工作。但是当您强制std::copy
时,就删除了正确ADL的选项。您不能拥有所有内容,而必须重新设计代码。
答案 5 :(得分:1)
(这些笔记现已集成到我对@sebrockm答案的编辑中)
为讨论起见,我将使用替代选项为自己的问题写一个答案。
这不是很好,因为它需要将所有N::
类包装在另一个模板类中(此处称为wrap
)。好消息是do_something
或N
类都需要了解特殊的N::copy
。代价是main
调用者必须显式包装N::
类,这很丑陋,但从耦合的角度来看是很好的,因为这是唯一应该了解整个系统的代码。
#include <iostream>
#include <algorithm>
#include <vector>
namespace N{
struct A{};
struct B{};
struct C{};
}
namespace N{
template<class S> struct wrap : S{};
template<class SomeN1, class SomeN2>
SomeN2 copy(wrap<SomeN1> first, wrap<SomeN1> last, wrap<SomeN2> d_first)
{
std::cout << "here" << std::endl;
return d_first;
}
}
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
using std::copy;
copy(first, second, d_first);
}
int main(){
N::wrap<N::A> a1, a2, a3;
std::cout << "do something in N:" << std::endl;
do_something(a1, a2, a3);
std::vector<int> v = {1,2,3};
std::vector<int> v2(3);
std::cout << "do something in std:" << std::endl;
do_something(std::begin(v), std::end(v), std::begin(v2));
for (int i : v2)
std::cout << i;
std::cout << std::endl;
}
答案 6 :(得分:-2)
建议您看看功能非常强大的新Boost.HOF库。
此功能完全可以满足您的要求:
#include <boost/hof.hpp>
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
namespace hof = boost::hof;
auto my_copy = hof::first_of(
[](auto first, auto second, auto d_first) -> decltype(N::copy(first, second, d_first))
{
return N::copy(first, second, d_first);
},
[](auto first, auto second, auto d_first) -> decltype(std::copy(first, second, d_first))
{
return std::copy(first, second, d_first);
});
my_copy(first, second, d_first);
}
hof::first_of
将选择其返回类型被推导为合法表达式的结果类型的第一个lambda。