查找,如果未找到,则返回默认值

turing-complete 2013-09-26 09:47:03
template <typename TContaienr, typename TKey, typename TValue>
const TValue& FindWithDefault(const TContaienr& c, const TKey& k, const TValue& v) {
const typename TContaienr::const_iterator iter = c.find(k);
if (iter == c.end()) {
return iter->second;
} else {
return v;
}
}


问题1:stl中或者boost中有没有与上面代码功能一样的工具类或者函数?
问题2:有没有好(代码少而美观)的type traits(不是重载)能够让这个函数既能用于map也能用于set?(目前这个只能用于map)
问题3:返回const TValue&有没有问题?会不会造成运行时错误?
...全文
508 17 打赏 收藏 转发到动态 举报
写回复
用AI写文章
17 条回复
切换为时间正序
请发表友善的回复…
发表回复
ri_aje 2013-09-28
  • 打赏
  • 举报
回复
引用 16 楼 mougaidong 的回复:
[quote=引用 15 楼 ri_aje 的回复:] I might have got a better idea. Will put it down later.
期待中。。。 目前来说,我已经折服了。
template<typename TKey>
float FindRateWithDefault(const boost::unordered_map<TKey, float>& rates,
                          const TKey& key,
                          const float default_value) {
  const typename boost::unordered_map<TKey, float>::const_iterator iter =
      rates.find(key);
  if (iter != rates.end()) {
    return iter->second;
  } else {
    return default_value;
  }
}
[/quote] 过奖了。 下面是我新改的,也修正了 #8 指出的问题。 先说一下逻辑,这样看起来更自然。 假设容器元素类型是 T,即 set<T> or map<...,T>,调用端通过 const reference 接收,且复制 T 需要尽量避免。 对于 FindWithDefault 那样的需求,需要确定返回值类型。 (1) 如果能够找到,或没找到但默认值是左值,则应该返回 const T&,避免复制。 (2) 如果没找到并且默认值是右值,则必须返回默认值的副本,避免出现 dangling reference. 问题是能够找到是运行时的信息,而返回类型必须编译期确定。 #7 的那个通过自己管理 raw storage,实现指针 (const T&) 和值 (T) 之间的按需变换。 这种方法有一些缺点: [1] 需要自己写实现,麻烦也容易出现疏漏。 [2] 按需变换仍然要使用 sizeof(T) and sizeof(T*) 两者中大的对象类型对应的存储空间,唯一节省的就是 T 的构造。 返回来看原来确定返回类型的逻辑,唯一的不和谐就在于,missing with rvalue default 的时候,默认值会在 full expression 结束时消失。如果能够想办法让它不消失,则返回类型能够确定为 const T& 或其类似的东西。对于这种情况,可以使用 new,通过堆空间过渡。这又带来了内存管理的问题,最省事儿的莫过于 std::unique_ptr or std::shared_ptr 了,唯一需要提供的就是一个自定义的 deleter,能够识别管理的对象是否需要由自身删除,这样 missing with rvalue default 的时候执行删除,其他的时候啥都不干就好了,因此就有了下面的代码。 不过这种方法也有问题: (a) 经常赶上 missing with rvalue default 的情况时,容易带来过多的小对象分配。 (b) 对于 trivially copyable T 类型,像 #16 给的那个 float 的用例,通过 new 完全没有必要。所有情况下都返回复制是最佳策略。 (b) 的解决方案比较容易,可以通过 is_trivially_copyable<T> 来分开不同的逻辑。不过这会带来调用端语法不统一的缺陷,i.e., pointer vs object access syntax. (a) 的就比较麻烦了。最好的貌似也就是把 FindWithDefault 设计成类,然后通过数据成员使用内存池降低消耗。 慢慢的这个问题越变越复杂,已经有过度设计的影子了。 写了这么多,我觉得最好的方案就是库函数和调用端配合,对于 trivially copyable 类型,直接返回副本。对于复制起来很贵的类型,强制调用端提供左值的默认值 (through function = delete construct),然后统一返回 const T&。

#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <unordered_set>
#include <unordered_map>

namespace
{
 namespace find_with_default_helper_t
 {
  template <typename> struct get_value_t;
  template <typename K, typename C, typename A>
  struct get_value_t<std::set<K,C,A>>
  {
   static K const& f (typename std::set<K,C,A>::const_iterator const& x)
   {
    return *x;
   }
  };
  template <typename K, typename H, typename E, typename A>
  struct get_value_t<std::unordered_set<K,H,E,A>>
  {
   static K const& f (typename std::unordered_set<K,H,E,A>::const_iterator const& x)
   {
    return *x;
   }
  };
  template <typename K, typename T, typename C, typename A>
  struct get_value_t<std::map<K,T,C,A>>
  {
   static T const& f (typename std::map<K,T,C,A>::const_iterator const& x)
   {
    return x->second;
   }
  };
  template <typename K, typename T, typename H, typename E, typename A>
  struct get_value_t<std::unordered_map<K,T,H,E,A>>
  {
   static T const& f (typename std::unordered_map<K,T,H,E,A>::const_iterator const& x)
   {
    return x->second;
   }
  };

  template <typename C>
  using element_t = typename std::decay<decltype(get_value_t<C>::f(std::declval<C>().begin()))>::type;

  template <typename C>
  element_t<C>const* conditional_copy (element_t<C>const& t) { return &t; }

  template <typename C>
  element_t<C>const* conditional_copy (element_t<C>&& t) { return new (element_t<C>) (std::move(t)); }

  template <typename T>
  struct conditional_deleter_t
  {
   conditional_deleter_t (bool x = false) : is_deletable(x) { }

   bool is_deletable;
   void operator () (T* ptr) const
   {
    if (is_deletable)
    {
     delete ptr;
    }
   }
  };

  template <typename T> using pointer_t = std::unique_ptr<T,conditional_deleter_t<T>>;
  // template <typename T> using pointer_t = std::shared_ptr<T>;
 }
}

template <typename C, typename K, typename T>
find_with_default_helper_t::pointer_t<find_with_default_helper_t::element_t<C>const>
FindWithDefault (const C& c, const K& k, T&& v)
{
 using element_t = find_with_default_helper_t::element_t<C>const;
 using pointer_t = find_with_default_helper_t::pointer_t<element_t>;
 using conditional_deleter_t = find_with_default_helper_t::conditional_deleter_t<element_t>;

 auto const it = c.find(k);
 if (it != c.end())
 {
  return pointer_t(&find_with_default_helper_t::get_value_t<C>::f(it),
                   conditional_deleter_t{false});
 }
 else
 {
  return pointer_t(find_with_default_helper_t::conditional_copy<C>(std::forward<T>(v)),
                   conditional_deleter_t{std::is_rvalue_reference<T&&>::value});
 }
}

struct marker_t
{
~marker_t ()
 {
  if (verbose)
  {
   std::cout << "@" << this << " dtor " << string << std::endl;
  }
 }
 marker_t (std::string const& s, bool const b = false) : string(s), verbose(b)
 {
  if (verbose)
  {
   std::cout << "@" << this << " ctor " << string << std::endl;
  }
 }
 marker_t (marker_t const& x) : string(x.string), verbose(x.verbose)
 {
  if (verbose)
  {
   std::cout << "@" << this << " copy ctor " << string << std::endl;
  }
 }
 marker_t (marker_t&& x) : string(std::move(x.string)), verbose(std::move(x.verbose))
 {
  if (verbose)
  {
   std::cout << "@" << this << " move ctor " << string << std::endl;
  }
 }

 std::string string;
 bool verbose = false;
};

int main ()
{
 std::unordered_map<size_t,marker_t>const container
 {
  {1,{"test",true}},
 };

 std::cout << std::endl;
 {
  marker_t const solid{"found && default-solid",true};
  auto const& ans = FindWithDefault(container,1,solid);
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
 {
  auto const& ans = FindWithDefault(container,1,marker_t{"found && default-temp",true});
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
 {
  marker_t const solid{"missing && default-solid",true};
  auto const& ans = FindWithDefault(container,0,solid);
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
 {
  auto const& ans = FindWithDefault(container,0,marker_t{"missing && default-temp",true});
  std::cout << "printing: " << ans->string << std::endl;

  // auto const aa = ans;
  // std::cout << "printing: " << aa->string << std::endl;
 }
 std::cout << std::endl;
}
turing-complete 2013-09-27
  • 打赏
  • 举报
回复
引用 13 楼 ri_aje 的回复:
[quote=引用 11 楼 mougaidong 的回复:] [quote=引用 7 楼 ri_aje 的回复:] [quote=引用 4 楼 mougaidong 的回复:] [quote=引用 3 楼 ri_aje 的回复:] #1 第二个例子找到的情况下会复制,不好,正在想方法解决。
好的。[/quote] 鼓捣了半天,也没啥即简洁又完善的方案。 主要是因为 rvalue 的问题返回值类型无法统一,有时候必须是值类型,有时候必须引用类型,且到底那种类型需要运行时信息 (c.find(k)==c.end()?) 才能确定。 这种时有时无的需求类似于 boost::optional,目测只能用 raw storage 解决。 扫了眼 boost::optional 也是这么干的,看来在 c++ 的框架下,只能这样了。 写了一个类似的东西,不过我觉得巨难看,放在下面了,万一你有兴趣,可以看一眼。 要是忽略 rvalue 的问题,就可以用 const T& 统一处理,唯一的缺点是调用端的返回值必须通过左值变量提供,写起来比较繁琐,如果这不是问题,那 const T& 是最好的方案了,只需要想办法禁止 rvalue 的调用即可,可以通过重载 = delete 实现。

#include <functional>
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <utility>

template <typename T>
struct optional_default_t
{
~optional_default_t () { destruct(); }

 optional_default_t (T&& t) : is_object(true) { construct(std::move(t)); }
 optional_default_t (T const& t) : optional_default_t(&t) { }
 optional_default_t (T const* p) : is_object(false) { construct(p); }

 optional_default_t (optional_default_t const& x) : is_object(x.is_object)
 {
  construct(x);
 }
 optional_default_t (optional_default_t&& x) : is_object(std::move(x.is_object))
 {
  construct(std::move(x));
 }
 optional_default_t& operator = (optional_default_t const& x)
 {
  if (this != x)
  {
   destruct();
   is_object = x.is_object;
   construct(x);
  }
 }
 optional_default_t& operator = (optional_default_t&& x)
 {
  if (this != &x)
  {
   destruct();
   is_object = std::move(x.is_object);
   construct(std::move(x));
  }
 }

 T const& get () const
 {
  return is_object?
         reinterpret_cast<T const&>(storage):
         reinterpret_cast<T const*const*>(&storage)[0][0];
 }
 operator T const& () const { return get(); }

 T const* operator->() const { return &get(); }
 T const* operator->()       { return &get(); }

private:
 using U = typename std::conditional<sizeof(T)<sizeof(T*),T*,T>::type;
 using S = typename std::aligned_storage<sizeof(U),std::alignment_of<U>::value>::type;

 bool is_object;
 S    storage;

 void      * address ()       { return &storage; }
 void const* address () const { return &storage; }

 void destruct () { if (is_object) { get().T::~T(); } }
 template <typename U>
 void construct (U&& u)
 {
  new (address()) (typename std::remove_reference<U>::type) (std::forward<U>(u));
 }
 void construct (optional_default_t const& x)
 {
  x.is_object? construct( x.get()):
               construct(&x.get());
 }
 void construct (optional_default_t&& x)
 {
  x.is_object? construct(std::move( x.get())):
               construct(std::move(&x.get()));
 }
};

template <typename T>
T const& get_value (T const& x)
{
 return x;
}

template <typename T, typename U>
U const& get_value (std::pair<T,U>const& x)
{
 return x.second;
}

template <typename C, typename K, typename T>
optional_default_t<typename std::decay<decltype(get_value(*std::declval<C>().begin()))>::type>
FindWithDefault(const C& c, const K& k, T&& v)
{
 auto const it = c.find(k);
 if (it != c.end())
 {
  return &get_value(*it);
 }
 else
 {
  return std::forward<T>(v);
 }
}

struct marker_t
{
~marker_t ()
 {
  if (verbose)
  {
   std::cout << "@" << this << " dtor " << string << std::endl;
  }
 }
 marker_t (std::string const& s, bool const b = false) : string(s), verbose(b)
 {
  if (verbose)
  {
   std::cout << "@" << this << " ctor " << string << std::endl;
  }
 }
 marker_t (marker_t const& x) : string(x.string), verbose(x.verbose)
 {
  if (verbose)
  {
   std::cout << "@" << this << " copy ctor " << string << std::endl;
  }
 }
 marker_t (marker_t&& x) : string(std::move(x.string)), verbose(std::move(x.verbose))
 {
  if (verbose)
  {
   std::cout << "@" << this << " move ctor " << string << std::endl;
  }
 }

 std::string string;
 bool verbose = false;
};

int main ()
{
 std::map<size_t,marker_t>const container
 {
  {1,{"test",true}},
 };

 std::cout << std::endl;
 {
  marker_t const solid{"found && default-solid",true};
  auto const ans = FindWithDefault(container,1,solid);
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
 {
  auto const ans = FindWithDefault(container,1,marker_t{"found && default-temp",true});
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
 {
  marker_t const solid{"missing && default-solid",true};
  auto const ans = FindWithDefault(container,0,solid);
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
 {
  auto const ans = FindWithDefault(container,0,marker_t{"missing && default-temp",true});
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
}
[/quote] 非常感谢,有没有完美的答案并不重要。 重要的是经过有心人的讨论研究,得出在现行条件下我们能解决到什么程度的结论。 这个工作还是很值得敬佩的,尤其我这边显示的回帖时间是北京时间凌晨三点多,不知道阁下的地方时是?[/quote] 我这里是下午,和北京刚好差 12 小时。 别误会,觉还是要睡的, 和别人交流也促使自己学习,这里经常能看到别人的精彩回复。 比如这个帖子让我认识到,提供默认值的难度(虽然这事儿看起来好像很没难度),从而也在一定程度上理解了标准为什么没有支持类似的功能。说到着,我记得前一阵看到过讨论,下一般标准好像要加类似于 boost::optional 的东西。[/quote] 原来是来自那半球的声音。
ri_aje 2013-09-27
  • 打赏
  • 举报
回复
引用 11 楼 mougaidong 的回复:
[quote=引用 7 楼 ri_aje 的回复:]
[quote=引用 4 楼 mougaidong 的回复:]
[quote=引用 3 楼 ri_aje 的回复:]
#1 第二个例子找到的情况下会复制,不好,正在想方法解决。

好的。[/quote]
鼓捣了半天,也没啥即简洁又完善的方案。
主要是因为 rvalue 的问题返回值类型无法统一,有时候必须是值类型,有时候必须引用类型,且到底那种类型需要运行时信息 (c.find(k)==c.end()?) 才能确定。
这种时有时无的需求类似于 boost::optional,目测只能用 raw storage 解决。
扫了眼 boost::optional 也是这么干的,看来在 c++ 的框架下,只能这样了。
写了一个类似的东西,不过我觉得巨难看,放在下面了,万一你有兴趣,可以看一眼。
要是忽略 rvalue 的问题,就可以用 const T& 统一处理,唯一的缺点是调用端的返回值必须通过左值变量提供,写起来比较繁琐,如果这不是问题,那 const T& 是最好的方案了,只需要想办法禁止 rvalue 的调用即可,可以通过重载 = delete 实现。

#include <functional>
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <utility>

template <typename T>
struct optional_default_t
{
~optional_default_t () { destruct(); }

optional_default_t (T&& t) : is_object(true) { construct(std::move(t)); }
optional_default_t (T const& t) : optional_default_t(&t) { }
optional_default_t (T const* p) : is_object(false) { construct(p); }

optional_default_t (optional_default_t const& x) : is_object(x.is_object)
{
construct(x);
}
optional_default_t (optional_default_t&& x) : is_object(std::move(x.is_object))
{
construct(std::move(x));
}
optional_default_t& operator = (optional_default_t const& x)
{
if (this != x)
{
destruct();
is_object = x.is_object;
construct(x);
}
}
optional_default_t& operator = (optional_default_t&& x)
{
if (this != &x)
{
destruct();
is_object = std::move(x.is_object);
construct(std::move(x));
}
}

T const& get () const
{
return is_object?
reinterpret_cast<T const&>(storage):
reinterpret_cast<T const*const*>(&storage)[0][0];
}
operator T const& () const { return get(); }

T const* operator->() const { return &get(); }
T const* operator->() { return &get(); }

private:
using U = typename std::conditional<sizeof(T)<sizeof(T*),T*,T>::type;
using S = typename std::aligned_storage<sizeof(U),std::alignment_of<U>::value>::type;

bool is_object;
S storage;

void * address () { return &storage; }
void const* address () const { return &storage; }

void destruct () { if (is_object) { get().T::~T(); } }
template <typename U>
void construct (U&& u)
{
new (address()) (typename std::remove_reference<U>::type) (std::forward<U>(u));
}
void construct (optional_default_t const& x)
{
x.is_object? construct( x.get()):
construct(&x.get());
}
void construct (optional_default_t&& x)
{
x.is_object? construct(std::move( x.get())):
construct(std::move(&x.get()));
}
};

template <typename T>
T const& get_value (T const& x)
{
return x;
}

template <typename T, typename U>
U const& get_value (std::pair<T,U>const& x)
{
return x.second;
}

template <typename C, typename K, typename T>
optional_default_t<typename std::decay<decltype(get_value(*std::declval<C>().begin()))>::type>
FindWithDefault(const C& c, const K& k, T&& v)
{
auto const it = c.find(k);
if (it != c.end())
{
return &get_value(*it);
}
else
{
return std::forward<T>(v);
}
}

struct marker_t
{
~marker_t ()
{
if (verbose)
{
std::cout << "@" << this << " dtor " << string << std::endl;
}
}
marker_t (std::string const& s, bool const b = false) : string(s), verbose(b)
{
if (verbose)
{
std::cout << "@" << this << " ctor " << string << std::endl;
}
}
marker_t (marker_t const& x) : string(x.string), verbose(x.verbose)
{
if (verbose)
{
std::cout << "@" << this << " copy ctor " << string << std::endl;
}
}
marker_t (marker_t&& x) : string(std::move(x.string)), verbose(std::move(x.verbose))
{
if (verbose)
{
std::cout << "@" << this << " move ctor " << string << std::endl;
}
}

std::string string;
bool verbose = false;
};

int main ()
{
std::map<size_t,marker_t>const container
{
{1,{"test",true}},
};

std::cout << std::endl;
{
marker_t const solid{"found && default-solid",true};
auto const ans = FindWithDefault(container,1,solid);
std::cout << "printing: " << ans->string << std::endl;
}
std::cout << std::endl;
{
auto const ans = FindWithDefault(container,1,marker_t{"found && default-temp",true});
std::cout << "printing: " << ans->string << std::endl;
}
std::cout << std::endl;
{
marker_t const solid{"missing && default-solid",true};
auto const ans = FindWithDefault(container,0,solid);
std::cout << "printing: " << ans->string << std::endl;
}
std::cout << std::endl;
{
auto const ans = FindWithDefault(container,0,marker_t{"missing && default-temp",true});
std::cout << "printing: " << ans->string << std::endl;
}
std::cout << std::endl;
}
[/quote]
非常感谢,有没有完美的答案并不重要。
重要的是经过有心人的讨论研究,得出在现行条件下我们能解决到什么程度的结论。
这个工作还是很值得敬佩的,尤其我这边显示的回帖时间是北京时间凌晨三点多,不知道阁下的地方时是?[/quote]
我这里是下午,和北京刚好差 12 小时。
别误会,觉还是要睡的,
和别人交流也促使自己学习,这里经常能看到别人的精彩回复。
比如这个帖子让我认识到,提供默认值的难度(虽然这事儿看起来好像很没难度),从而也在一定程度上理解了标准为什么没有支持类似的功能。说到着,我记得前一阵看到过讨论,下一般标准好像要加类似于 boost::optional 的东西。
To_be_sky 2013-09-27
  • 打赏
  • 举报
回复
一楼太帅了!什么时候我也能写出这样的代码就好了。 学习下。
turing-complete 2013-09-27
  • 打赏
  • 举报
回复
引用 7 楼 ri_aje 的回复:
[quote=引用 4 楼 mougaidong 的回复:] [quote=引用 3 楼 ri_aje 的回复:] #1 第二个例子找到的情况下会复制,不好,正在想方法解决。
好的。[/quote] 鼓捣了半天,也没啥即简洁又完善的方案。 主要是因为 rvalue 的问题返回值类型无法统一,有时候必须是值类型,有时候必须引用类型,且到底那种类型需要运行时信息 (c.find(k)==c.end()?) 才能确定。 这种时有时无的需求类似于 boost::optional,目测只能用 raw storage 解决。 扫了眼 boost::optional 也是这么干的,看来在 c++ 的框架下,只能这样了。 写了一个类似的东西,不过我觉得巨难看,放在下面了,万一你有兴趣,可以看一眼。 要是忽略 rvalue 的问题,就可以用 const T& 统一处理,唯一的缺点是调用端的返回值必须通过左值变量提供,写起来比较繁琐,如果这不是问题,那 const T& 是最好的方案了,只需要想办法禁止 rvalue 的调用即可,可以通过重载 = delete 实现。

#include <functional>
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <utility>

template <typename T>
struct optional_default_t
{
~optional_default_t () { destruct(); }

 optional_default_t (T&& t) : is_object(true) { construct(std::move(t)); }
 optional_default_t (T const& t) : optional_default_t(&t) { }
 optional_default_t (T const* p) : is_object(false) { construct(p); }

 optional_default_t (optional_default_t const& x) : is_object(x.is_object)
 {
  construct(x);
 }
 optional_default_t (optional_default_t&& x) : is_object(std::move(x.is_object))
 {
  construct(std::move(x));
 }
 optional_default_t& operator = (optional_default_t const& x)
 {
  if (this != x)
  {
   destruct();
   is_object = x.is_object;
   construct(x);
  }
 }
 optional_default_t& operator = (optional_default_t&& x)
 {
  if (this != &x)
  {
   destruct();
   is_object = std::move(x.is_object);
   construct(std::move(x));
  }
 }

 T const& get () const
 {
  return is_object?
         reinterpret_cast<T const&>(storage):
         reinterpret_cast<T const*const*>(&storage)[0][0];
 }
 operator T const& () const { return get(); }

 T const* operator->() const { return &get(); }
 T const* operator->()       { return &get(); }

private:
 using U = typename std::conditional<sizeof(T)<sizeof(T*),T*,T>::type;
 using S = typename std::aligned_storage<sizeof(U),std::alignment_of<U>::value>::type;

 bool is_object;
 S    storage;

 void      * address ()       { return &storage; }
 void const* address () const { return &storage; }

 void destruct () { if (is_object) { get().T::~T(); } }
 template <typename U>
 void construct (U&& u)
 {
  new (address()) (typename std::remove_reference<U>::type) (std::forward<U>(u));
 }
 void construct (optional_default_t const& x)
 {
  x.is_object? construct( x.get()):
               construct(&x.get());
 }
 void construct (optional_default_t&& x)
 {
  x.is_object? construct(std::move( x.get())):
               construct(std::move(&x.get()));
 }
};

template <typename T>
T const& get_value (T const& x)
{
 return x;
}

template <typename T, typename U>
U const& get_value (std::pair<T,U>const& x)
{
 return x.second;
}

template <typename C, typename K, typename T>
optional_default_t<typename std::decay<decltype(get_value(*std::declval<C>().begin()))>::type>
FindWithDefault(const C& c, const K& k, T&& v)
{
 auto const it = c.find(k);
 if (it != c.end())
 {
  return &get_value(*it);
 }
 else
 {
  return std::forward<T>(v);
 }
}

struct marker_t
{
~marker_t ()
 {
  if (verbose)
  {
   std::cout << "@" << this << " dtor " << string << std::endl;
  }
 }
 marker_t (std::string const& s, bool const b = false) : string(s), verbose(b)
 {
  if (verbose)
  {
   std::cout << "@" << this << " ctor " << string << std::endl;
  }
 }
 marker_t (marker_t const& x) : string(x.string), verbose(x.verbose)
 {
  if (verbose)
  {
   std::cout << "@" << this << " copy ctor " << string << std::endl;
  }
 }
 marker_t (marker_t&& x) : string(std::move(x.string)), verbose(std::move(x.verbose))
 {
  if (verbose)
  {
   std::cout << "@" << this << " move ctor " << string << std::endl;
  }
 }

 std::string string;
 bool verbose = false;
};

int main ()
{
 std::map<size_t,marker_t>const container
 {
  {1,{"test",true}},
 };

 std::cout << std::endl;
 {
  marker_t const solid{"found && default-solid",true};
  auto const ans = FindWithDefault(container,1,solid);
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
 {
  auto const ans = FindWithDefault(container,1,marker_t{"found && default-temp",true});
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
 {
  marker_t const solid{"missing && default-solid",true};
  auto const ans = FindWithDefault(container,0,solid);
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
 {
  auto const ans = FindWithDefault(container,0,marker_t{"missing && default-temp",true});
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
}
[/quote] 非常感谢,有没有完美的答案并不重要。 重要的是经过有心人的讨论研究,得出在现行条件下我们能解决到什么程度的结论。 这个工作还是很值得敬佩的,尤其我这边显示的回帖时间是北京时间凌晨三点多,不知道阁下的地方时是?
ri_aje 2013-09-27
  • 打赏
  • 举报
回复
引用 8 楼 FancyMouse 的回复:
1L的代码估计碰到set<pair<>>的时候会吃瘪。 >有没有好(代码少而美观)的type traits(不是重载)能够让这个函数既能用于map也能用于set?(目前这个只能用于map) 但是你仔细想想,set你如果想返回key,map你却返回value,这已经是属于功能上的不同了。准确的说,对于map来说,你不想返回默认的key value pair而只想要value。那么对于这种情况,我觉得get_value不能针对value_type来template,而得用iterator type来template。这样就能避免set<pair<>>的时候只返回pair里第二个数的情况。 用iterator的话也就不用关心拷贝性能了。拷一个iterator能有多少perf penalty。
good point. 想偷个懒儿还不行,看来还是得按照 iterator 的类型来。
FancyMouse 2013-09-27
  • 打赏
  • 举报
回复
哦不对。你们讨论的拷贝性能问题是在返回值那里。那我那个做法不能解决这个问题。这点上还是看7L吧。
FancyMouse 2013-09-27
  • 打赏
  • 举报
回复
1L的代码估计碰到set<pair<>>的时候会吃瘪。 >有没有好(代码少而美观)的type traits(不是重载)能够让这个函数既能用于map也能用于set?(目前这个只能用于map) 但是你仔细想想,set你如果想返回key,map你却返回value,这已经是属于功能上的不同了。准确的说,对于map来说,你不想返回默认的key value pair而只想要value。那么对于这种情况,我觉得get_value不能针对value_type来template,而得用iterator type来template。这样就能避免set<pair<>>的时候只返回pair里第二个数的情况。 用iterator的话也就不用关心拷贝性能了。拷一个iterator能有多少perf penalty。
ri_aje 2013-09-27
  • 打赏
  • 举报
回复
引用 4 楼 mougaidong 的回复:
[quote=引用 3 楼 ri_aje 的回复:] #1 第二个例子找到的情况下会复制,不好,正在想方法解决。
好的。[/quote] 鼓捣了半天,也没啥即简洁又完善的方案。 主要是因为 rvalue 的问题返回值类型无法统一,有时候必须是值类型,有时候必须引用类型,且到底那种类型需要运行时信息 (c.find(k)==c.end()?) 才能确定。 这种时有时无的需求类似于 boost::optional,目测只能用 raw storage 解决。 扫了眼 boost::optional 也是这么干的,看来在 c++ 的框架下,只能这样了。 写了一个类似的东西,不过我觉得巨难看,放在下面了,万一你有兴趣,可以看一眼。 要是忽略 rvalue 的问题,就可以用 const T& 统一处理,唯一的缺点是调用端的返回值必须通过左值变量提供,写起来比较繁琐,如果这不是问题,那 const T& 是最好的方案了,只需要想办法禁止 rvalue 的调用即可,可以通过重载 = delete 实现。

#include <functional>
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <utility>

template <typename T>
struct optional_default_t
{
~optional_default_t () { destruct(); }

 optional_default_t (T&& t) : is_object(true) { construct(std::move(t)); }
 optional_default_t (T const& t) : optional_default_t(&t) { }
 optional_default_t (T const* p) : is_object(false) { construct(p); }

 optional_default_t (optional_default_t const& x) : is_object(x.is_object)
 {
  construct(x);
 }
 optional_default_t (optional_default_t&& x) : is_object(std::move(x.is_object))
 {
  construct(std::move(x));
 }
 optional_default_t& operator = (optional_default_t const& x)
 {
  if (this != x)
  {
   destruct();
   is_object = x.is_object;
   construct(x);
  }
 }
 optional_default_t& operator = (optional_default_t&& x)
 {
  if (this != &x)
  {
   destruct();
   is_object = std::move(x.is_object);
   construct(std::move(x));
  }
 }

 T const& get () const
 {
  return is_object?
         reinterpret_cast<T const&>(storage):
         reinterpret_cast<T const*const*>(&storage)[0][0];
 }
 operator T const& () const { return get(); }

 T const* operator->() const { return &get(); }
 T const* operator->()       { return &get(); }

private:
 using U = typename std::conditional<sizeof(T)<sizeof(T*),T*,T>::type;
 using S = typename std::aligned_storage<sizeof(U),std::alignment_of<U>::value>::type;

 bool is_object;
 S    storage;

 void      * address ()       { return &storage; }
 void const* address () const { return &storage; }

 void destruct () { if (is_object) { get().T::~T(); } }
 template <typename U>
 void construct (U&& u)
 {
  new (address()) (typename std::remove_reference<U>::type) (std::forward<U>(u));
 }
 void construct (optional_default_t const& x)
 {
  x.is_object? construct( x.get()):
               construct(&x.get());
 }
 void construct (optional_default_t&& x)
 {
  x.is_object? construct(std::move( x.get())):
               construct(std::move(&x.get()));
 }
};

template <typename T>
T const& get_value (T const& x)
{
 return x;
}

template <typename T, typename U>
U const& get_value (std::pair<T,U>const& x)
{
 return x.second;
}

template <typename C, typename K, typename T>
optional_default_t<typename std::decay<decltype(get_value(*std::declval<C>().begin()))>::type>
FindWithDefault(const C& c, const K& k, T&& v)
{
 auto const it = c.find(k);
 if (it != c.end())
 {
  return &get_value(*it);
 }
 else
 {
  return std::forward<T>(v);
 }
}

struct marker_t
{
~marker_t ()
 {
  if (verbose)
  {
   std::cout << "@" << this << " dtor " << string << std::endl;
  }
 }
 marker_t (std::string const& s, bool const b = false) : string(s), verbose(b)
 {
  if (verbose)
  {
   std::cout << "@" << this << " ctor " << string << std::endl;
  }
 }
 marker_t (marker_t const& x) : string(x.string), verbose(x.verbose)
 {
  if (verbose)
  {
   std::cout << "@" << this << " copy ctor " << string << std::endl;
  }
 }
 marker_t (marker_t&& x) : string(std::move(x.string)), verbose(std::move(x.verbose))
 {
  if (verbose)
  {
   std::cout << "@" << this << " move ctor " << string << std::endl;
  }
 }

 std::string string;
 bool verbose = false;
};

int main ()
{
 std::map<size_t,marker_t>const container
 {
  {1,{"test",true}},
 };

 std::cout << std::endl;
 {
  marker_t const solid{"found && default-solid",true};
  auto const ans = FindWithDefault(container,1,solid);
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
 {
  auto const ans = FindWithDefault(container,1,marker_t{"found && default-temp",true});
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
 {
  marker_t const solid{"missing && default-solid",true};
  auto const ans = FindWithDefault(container,0,solid);
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
 {
  auto const ans = FindWithDefault(container,0,marker_t{"missing && default-temp",true});
  std::cout << "printing: " << ans->string << std::endl;
 }
 std::cout << std::endl;
}
turing-complete 2013-09-27
  • 打赏
  • 举报
回复
引用 15 楼 ri_aje 的回复:
I might have got a better idea. Will put it down later.
期待中。。。 目前来说,我已经折服了。
template<typename TKey>
float FindRateWithDefault(const boost::unordered_map<TKey, float>& rates,
                          const TKey& key,
                          const float default_value) {
  const typename boost::unordered_map<TKey, float>::const_iterator iter =
      rates.find(key);
  if (iter != rates.end()) {
    return iter->second;
  } else {
    return default_value;
  }
}
ri_aje 2013-09-27
  • 打赏
  • 举报
回复
I might have got a better idea. Will put it down later.
modyaj 2013-09-26
  • 打赏
  • 举报
回复
一楼真霸气
healer_kx 2013-09-26
  • 打赏
  • 举报
回复
顶一楼:) 我觉得我大概也会这么处理的。
turing-complete 2013-09-26
  • 打赏
  • 举报
回复
引用 3 楼 ri_aje 的回复:
#1 第二个例子找到的情况下会复制,不好,正在想方法解决。
好的。
ri_aje 2013-09-26
  • 打赏
  • 举报
回复
#1 第二个例子找到的情况下会复制,不好,正在想方法解决。
aizibion 2013-09-26
  • 打赏
  • 举报
回复
我眼睛花了吗? const typename TContaienr::const_iterator iter = c.find(k); if (iter == c.end()) { return iter->second; } else { return v; } 这个怎么看都是错误代码啊,end还返还啥啊?不end直接返回传入参数? 不太懂啊
ri_aje 2013-09-26
  • 打赏
  • 举报
回复
0. 先改错。 拼写错误,TContaienr -> TContainer 逻辑错误 if (iter == c.end()) -> if (iter != c.end()) 1. stl 中应该没有,boost 中不知道。 2. 如果只需要针对 set/map,唯一的区别就是如何从一个 iterator 获取元素的问题,这个可以增加中间层解决,见下面代码 get_value,是否美观就不清楚了。 3. 可能有问题,见下面代码中的 ub_test。问题在于 const T& 能够结合在临时变量上,后者如果返回的话,而且返回端使用引用接收,当 full expression 结束以后返回端的引用就是 dangling reference 了。

#include <map>
#include <iostream>
#include <string>

template <typename T>
T const& get_value (T const& x)
{
 return x;
}

template <typename T, typename U>
U const& get_value (std::pair<T,U>const& x)
{
 return x.second;
}

template <typename TContainer, typename TKey, typename TValue>
const TValue& FindWithDefault(const TContainer& c, const TKey& k, const TValue& v) {
  const typename TContainer::const_iterator iter = c.find(k);
  if (iter != c.end()) {
   return get_value(*iter);
    // return iter->second;
  } else {
    return v;
  }
}

struct test_t
{
~test_t ()
 {
  if (verbose)
  {
   std::cout << "@" << this << " dtor " << string << std::endl;
  }
 }
 test_t (std::string const& s, bool const b = false) : string(s), verbose(b)
 {
  if (verbose)
  {
   std::cout << "@" << this << " ctor " << string << std::endl;
  }
 }

 std::string string;
 bool verbose = false;
};

int main ()
{
 std::map<size_t,test_t>const test
 {
  {1,{"test"}},
  {2,{"retest"}},
 };

 // temporary variable destructed at the end of ;
 test_t const& ub_test = FindWithDefault(test,3,test_t{"default",true}); 
 // UB, attempting to access data member of destructed object.
 std::cout << "printing: " << ub_test.string << std::endl;
}
在我这里(g++-4.8.1)的输出结果是: @0x7fff4b8aa1f0 ctor default @0x7fff4b8aa1f0 dtor default printing: 可见 printing 之前对象已经析构了。 这里可以用 perfect forward 解决。见下面代码中的两个 ans 例子。 第一个使用临时变量,通过 move 返回默认值;第二个使用本地变量,直接抓取的引用。

#include <map>
#include <iostream>
#include <string>

template <typename T>
T const& get_value (T const& x)
{
 return x;
}

template <typename T, typename U>
U const& get_value (std::pair<T,U>const& x)
{
 return x.second;
}

template <typename TContainer, typename TKey, typename TValue>
TValue FindWithDefault(const TContainer& c, const TKey& k, TValue&& v) {
  const typename TContainer::const_iterator iter = c.find(k);
  if (iter != c.end()) {
   return get_value(*iter);
    // return iter->second;
  } else {
    return std::forward<TValue>(v);
  }
}

struct test_t
{
~test_t ()
 {
  if (verbose)
  {
   std::cout << "@" << this << " dtor " << string << std::endl;
  }
 }
 test_t (std::string const& s, bool const b = false) : string(s), verbose(b)
 {
  if (verbose)
  {
   std::cout << "@" << this << " ctor " << string << std::endl;
  }
 }
 test_t (test_t const& x) : string(x.string), verbose(x.verbose)
 {
  if (verbose)
  {
   std::cout << "@" << this << " copy ctor " << string << std::endl;
  }
 }
 test_t (test_t&& x) : string(std::move(x.string)), verbose(std::move(x.verbose))
 {
  if (verbose)
  {
   std::cout << "@" << this << " move ctor " << string << std::endl;
  }
 }

 std::string string;
 bool verbose = false;
};

int main ()
{
 std::map<size_t,test_t>const map
 {
  {1,{"map"}},
  {2,{"retest"}},
 };

 {
  test_t const& ans = FindWithDefault(map,3,test_t{"temp",true});
  std::cout << "printing: " << ans.string << std::endl;
 }
 std::cout << std::endl;

 {
  test_t const solid{"solid",true};
  test_t const& ans = FindWithDefault(map,3,solid);
  std::cout << "printing: " << ans.string << std::endl;
 }
}
我这里输出结果: @0x7fff8fe3ac20 ctor temp @0x7fff8fe3abe0 move ctor temp @0x7fff8fe3ac20 dtor printing: temp @0x7fff8fe3abe0 dtor temp @0x7fff8fe3ac20 ctor solid printing: solid @0x7fff8fe3ac20 dtor solid

64,643

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧