【问题标题】:How can I use void template type for compile-time conditional code and static_assert?如何将 void 模板类型用于编译时条件代码和 static_assert?
【发布时间】:2020-09-15 10:10:47
【问题描述】:

我有一个遍历函数,它为每个元素调用回调,并希望在编译时使用非默认参数实现它的逻辑扩展,如下所示:

#include <type_traits>
#include <iostream>
using namespace std;

template<
    typename Odds = void,
    typename Evens = void,
    typename Skip = void
>
void iterate(
    const auto & on_element,
    const auto & skip 
) {
    for ( int i = 0; i < 6; ++ i ) {
        if constexpr ( ! is_same_v< Skip, void > ) {
            if ( skip.find( i ) != skip.end() )
                continue;
        }

        if ( i % 2 == 1 ) {
            if constexpr ( is_same_v< Odds, void > )
                on_element( i );
        }
        else {
            if constexpr ( is_same_v< Evens, void > )
                on_element( i );
        }

        static_assert(
            is_same_v< Odds, void > || is_same_v< Evens, void >,
            "Either odds or evens are required to get traversed"
        );
    }
}

int main() {
    const auto & on_element = []( const int & i ) {
        cout << "    " << i << endl;
    };

    cout << "Whole range:" << endl;
    iterate( on_element );

    cout << "Should skip specified elements:" << endl;
    iterate( on_element, { 1, 2, 3 } );

    cout << "Should traverse only evens:" << endl;
    iterate< bool >( on_element );

    cout << "Should traverse only odds:" << endl;
    iterate< void, bool >( on_element );

    //should NOT compile if uncomment:
    //iterate< bool, bool >( on_element );
}

但它不能用g++ -std=gnu++2a -fconcepts main.cpp -o main编译:

main.cpp: In function ‘int main()’:
main.cpp:42:25: error: no matching function for call to ‘iterate(const main()::<lambda(const int&)>&)’
   42 |     iterate( on_element );
      |                         ^
main.cpp:10:6: note: candidate: ‘template<class Odds, class Evens, class Skip, class auto:11, class auto:12> void iterate(const auto:11&, const auto:12&)’
   10 | void iterate(
      |      ^~~~~~~
main.cpp:10:6: note:   template argument deduction/substitution failed:
main.cpp:42:25: note:   candidate expects 2 arguments, 1 provided
   42 |     iterate( on_element );
      |                         ^
main.cpp:45:38: error: no matching function for call to ‘iterate(const main()::<lambda(const int&)>&, <brace-enclosed initializer list>)’
   45 |     iterate( on_element, { 1, 2, 3 } );
      |                                      ^
main.cpp:10:6: note: candidate: ‘template<class Odds, class Evens, class Skip, class auto:11, class auto:12> void iterate(const auto:11&, const auto:12&)’
   10 | void iterate(
      |      ^~~~~~~
main.cpp:10:6: note:   template argument deduction/substitution failed:
main.cpp:45:38: note:   couldn’t deduce template parameter ‘auto:12’
   45 |     iterate( on_element, { 1, 2, 3 } );
      |                                      ^
main.cpp:48:33: error: no matching function for call to ‘iterate<bool>(const main()::<lambda(const int&)>&)’
   48 |     iterate< bool >( on_element );
      |                                 ^
main.cpp:10:6: note: candidate: ‘template<class Odds, class Evens, class Skip, class auto:11, class auto:12> void iterate(const auto:11&, const auto:12&)’
   10 | void iterate(
      |      ^~~~~~~
main.cpp:10:6: note:   template argument deduction/substitution failed:
main.cpp:48:33: note:   candidate expects 2 arguments, 1 provided
   48 |     iterate< bool >( on_element );
      |                                 ^
main.cpp:51:39: error: no matching function for call to ‘iterate<void, bool>(const main()::<lambda(const int&)>&)’
   51 |     iterate< void, bool >( on_element );
      |                                       ^
main.cpp:10:6: note: candidate: ‘template<class Odds, class Evens, class Skip, class auto:11, class auto:12> void iterate(const auto:11&, const auto:12&)’
   10 | void iterate(
      |      ^~~~~~~
main.cpp:10:6: note:   template argument deduction/substitution failed:
main.cpp:51:39: note:   candidate expects 2 arguments, 1 provided
   51 |     iterate< void, bool >( on_element );
      |                                       ^

有没有办法在 C++ 中实现它? 也许有更好的编译时技术来实现所需的行为?

【问题讨论】:

  • 由于除了针对void 进行测试之外,您没有将模板类型用于任何其他用途,因此您应该考虑使用bool 模板参数。
  • @IlCapitano 我想我在精神上坚持auto 太多,无法弄清楚如何检查参数是否已通过^^,但确实是bool T = true。感谢您指出这一点!

标签: c++ templates c++20 template-argument-deduction


【解决方案1】:
template<
  bool Odds = false,
  bool Evens = false,
  bool Skip = false // or really, true: why pass skip if we won't use it?
>
void iterate(
  const auto & on_element,
  const auto & skip
)

并编写另一个重载:

template<
  bool Odds = false,
  bool Evens = false
> // no bool Skip; lack of arg is enough
void iterate(
  const auto & on_element
)
{
  iterate<Odds, Evens, false>(on_element);
}

然后将! is_same_v&lt; Skip, void &gt; 替换为Skip 和类似的赔率/偶数。

还有

static_assert( !Skip||!std::is_same_v<decltype(skip), const int&>, "You must pass a 2nd argument" );

对生活质量有好处。

【讨论】:

  • 对不起,但是......调用iterate(on_element)(没有第二个参数)我得到一个“错误:没有匹配函数调用'iterate(const main()::&)'", "note: could't deduc template parameter 'auto:12'" from g++ 11.0.0 and a similar error ("candidate template denied: could't infer template argument 'skip:auto'") from clang++ 12.0.0(魔杖盒)。您确定在 C++20 中可以为 auto 参数表示默认值吗?
  • @max 不,不确定;现已修复
【解决方案2】:

在我看来,在这种情况下,您应该避免使用 auto 类型参数。

我提出以下代码

#include <type_traits>
#include <iostream>
#include <set>

using namespace std;

template<
    bool Odds = true,
    bool Evens = true,
    typename Func,
    typename Skip = bool
>
void iterate(
    Func const & on_element,
    Skip const & skip = false
) {
    for ( int i = 0; i < 6; ++ i ) {
        if constexpr ( ! is_same_v< Skip, bool > ) {
            if ( skip.find( i ) != skip.end() )
                continue;
        }

        if ( i % 2 == 1 ) {
            if constexpr ( Odds )
                on_element( i );
        }
        else {
            if constexpr ( Evens )
                on_element( i );
        }

        static_assert(
            Odds || Evens,
            "Either odds or evens are required to get traversed"
        );
    }
}

int main() {
    const auto & on_element = []( const int & i ) {
        cout << "    " << i << endl;
    };

    cout << "Whole range:" << endl;
    iterate( on_element );

    cout << "Should skip specified elements:" << endl;
    iterate( on_element, std::set{ 1, 2, 3 } );

    cout << "Should traverse only evens:" << endl;
    iterate< false >( on_element );

    cout << "Should traverse only odds:" << endl;
    iterate< true, false >( on_element );

    // should NOT compile if uncomment:
    // iterate< false, false >( on_element );
}

【讨论】:

  • 我可能会使用enum class Parity {Odd, Even, Both}; 以避免不可能的组合。
【解决方案3】:

也许简化

#include <type_traits>
#include <iostream>
#include <algorithm>
#include <array>

auto Odds = [](auto&& i) { return i % 2 == 0; };
auto Evens = [](auto&& i) { return i % 2 == 1; };
auto None = [](auto&&) { return false; };

template<class...Ts>
struct These
{
    constexpr These(Ts... args)
    : args_ { args... }
    {}

    template<class Arg>
    bool operator()(Arg&& i) const
    {
        return std::any_of(
            std::begin(args_), 
            std::end(args_), 
            [i](auto x) { return x == i; }
        );
    }

    std::array<std::common_type_t<Ts...>, sizeof...(Ts)> args_;
};

template<
    typename Skip = decltype(None)
>
void iterate(
    const auto & on_element,
    Skip&& skip = Skip()
) {
    for ( int i = 0; i < 6; ++ i ) {
        if (skip(i))
            continue;
        std::cout << i << ", ";
    }
    std::cout << std::endl;
}

int main() {
    const auto & on_element = []( const int & i ) {
        std::cout << "    " << i << std::endl;
    };

    std::cout << "Whole range:" << std::endl;
    iterate( on_element );

    std::cout << "Should skip specified elements:" << std::endl;
    iterate( on_element, These( 1, 2, 3 ));

    std::cout << "Should traverse only evens:" << std::endl;
    iterate( on_element , Evens);

    std::cout << "Should traverse only odds:" << std::endl;
    iterate( on_element, Odds );
}

预期输出:

Whole range:

0, 1, 2, 3, 4, 5, 

Should skip specified elements:

0, 4, 5, 

Should traverse only evens:

0, 2, 4, 

Should traverse only odds:

1, 3, 5, 

【讨论】:

  • 整个跳过的概念被抽象出来了,酷!谢谢你的回答!
【解决方案4】:

如果您需要iterate 的版本来获取一个参数,则必须实现一个。

【讨论】:

  • 或者您可能想要第二个参数的默认值,例如空列表
猜你喜欢
  • 1970-01-01
  • 2014-06-17
  • 1970-01-01
  • 1970-01-01
  • 2011-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多