admin管理员组

文章数量:1404595

As a minimal example (this is part of a larger project), I have the following three files in a project with the following structure:

.
├── src
│   ├── binary_search.cpp
└── test
    ├── CMakeLists.txt
    ├── build
    └── test_binary_search.cpp

binary_search.cpp:

#include <cstdint> 

template <typename T> T binary_search(T A[], uint8_t len, T nu) {
  uint8_t low = 0;
  uint8_t high = len - 1;

  while (low <= high) {
    uint8_t mid = low + (high - low) / 2;

    if (nu == A[mid])
      return mid;
    else if (nu > A[mid])
      low = mid + 1;
    else
      high = mid - 1;
  }

  return -1;
}

test_binary_search.cpp:

#include <boost/test/data/test_case.hpp>
#include <boost/test/included/unit_test.hpp>

#define BOOST_TEST_MODULE test_algorithms

BOOST_AUTO_TEST_SUITE(test_search)

int32_t input_arrs[4][8] = {{26, 31, 41, 41, 58, 59, 101, 104},
                            {26, 31, 41, 41, 58, 59, 101, 104},
                            {1, 4, 5, 7, 19, 28, 45, 92},
                            {1, 4, 5, 7, 19, 28, 45, 92}};
int32_t input_vals[] = {31, 32, 92, 101};
int32_t expected_vals[] = {1, -1, 7, -1};

auto test_cases = boost::unit_test::data::make(input_arrs) ^
                  boost::unit_test::data::make(input_vals) ^
                  boost::unit_test::data::make(expected_vals);

BOOST_DATA_TEST_CASE(test_binary_search, test_cases, input_arr, nu, exp) {
  auto obs = binary_search(input_arr, 8, nu);

  BOOST_ASSERT(obs == exp);
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++20")
project(test_algorithms)

set(SOURCES 
    ../src/binary_search.cpp
    test_binary_search.cpp
)

find_package(Boost 1.87.0 REQUIRED COMPONENTS unit_test_framework)
if (Boost_FOUND)
    enable_testing()
    include_directories(${Boost_INCLUDE_DIRS})

    add_executable(test_binary_search ${SOURCES})
    target_include_directories(test_binary_search PRIVATE ${Boost_INCLUDE_DIRS})
    add_test(NAME test_binary_search COMMAND test_binary_search)
endif()

I am attempting to compile this on a MacBook Pro M3 Pro running MacOS Sequoia 15.3.1 using the following commands:

cmake -S . -B build
cmake --build build

after which I receive a very long error message. I suspect that the problem might be

/Users/graham/dev/cpp_experiments/test/test_binary_search.cpp:20:14: error: use of undeclared identifier 'binary_search'
   20 |   auto obs = binary_search(input_arr, 8, nu);

which may in turn be caused by

/opt/homebrew/include/boost/test/data/test_case.hpp:89:16: error: reference to type 'const const int[8]' could not bind to an lvalue of type 'const int'
   89 |         return *value;

I'm not sure where/how I'm casting to type const int in this code; are the Boost macros are performing this conversion? I'm struggling to understand the recommended way to use boost::unit_test::data::make to zip up the parameterized test data. Any help in how to better structure this code to create a parameterized test suite would be greatly appreciated!

As a minimal example (this is part of a larger project), I have the following three files in a project with the following structure:

.
├── src
│   ├── binary_search.cpp
└── test
    ├── CMakeLists.txt
    ├── build
    └── test_binary_search.cpp

binary_search.cpp:

#include <cstdint> 

template <typename T> T binary_search(T A[], uint8_t len, T nu) {
  uint8_t low = 0;
  uint8_t high = len - 1;

  while (low <= high) {
    uint8_t mid = low + (high - low) / 2;

    if (nu == A[mid])
      return mid;
    else if (nu > A[mid])
      low = mid + 1;
    else
      high = mid - 1;
  }

  return -1;
}

test_binary_search.cpp:

#include <boost/test/data/test_case.hpp>
#include <boost/test/included/unit_test.hpp>

#define BOOST_TEST_MODULE test_algorithms

BOOST_AUTO_TEST_SUITE(test_search)

int32_t input_arrs[4][8] = {{26, 31, 41, 41, 58, 59, 101, 104},
                            {26, 31, 41, 41, 58, 59, 101, 104},
                            {1, 4, 5, 7, 19, 28, 45, 92},
                            {1, 4, 5, 7, 19, 28, 45, 92}};
int32_t input_vals[] = {31, 32, 92, 101};
int32_t expected_vals[] = {1, -1, 7, -1};

auto test_cases = boost::unit_test::data::make(input_arrs) ^
                  boost::unit_test::data::make(input_vals) ^
                  boost::unit_test::data::make(expected_vals);

BOOST_DATA_TEST_CASE(test_binary_search, test_cases, input_arr, nu, exp) {
  auto obs = binary_search(input_arr, 8, nu);

  BOOST_ASSERT(obs == exp);
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++20")
project(test_algorithms)

set(SOURCES 
    ../src/binary_search.cpp
    test_binary_search.cpp
)

find_package(Boost 1.87.0 REQUIRED COMPONENTS unit_test_framework)
if (Boost_FOUND)
    enable_testing()
    include_directories(${Boost_INCLUDE_DIRS})

    add_executable(test_binary_search ${SOURCES})
    target_include_directories(test_binary_search PRIVATE ${Boost_INCLUDE_DIRS})
    add_test(NAME test_binary_search COMMAND test_binary_search)
endif()

I am attempting to compile this on a MacBook Pro M3 Pro running MacOS Sequoia 15.3.1 using the following commands:

cmake -S . -B build
cmake --build build

after which I receive a very long error message. I suspect that the problem might be

/Users/graham/dev/cpp_experiments/test/test_binary_search.cpp:20:14: error: use of undeclared identifier 'binary_search'
   20 |   auto obs = binary_search(input_arr, 8, nu);

which may in turn be caused by

/opt/homebrew/include/boost/test/data/test_case.hpp:89:16: error: reference to type 'const const int[8]' could not bind to an lvalue of type 'const int'
   89 |         return *value;

I'm not sure where/how I'm casting to type const int in this code; are the Boost macros are performing this conversion? I'm struggling to understand the recommended way to use boost::unit_test::data::make to zip up the parameterized test data. Any help in how to better structure this code to create a parameterized test suite would be greatly appreciated!

Share Improve this question edited Mar 11 at 8:11 usr1234567 23.6k19 gold badges123 silver badges140 bronze badges asked Mar 10 at 16:35 grahamhsgrahamhs 336 bronze badges 8
  • You got the error use of undeclared identifier 'binary_search' because definition of the template function binary_search is inaccessible when you compile test_binary_search.cpp. With separate test file you cannot test things which are defined only in a source file. You may test only functionality declared in a header file (which you could include into the test file.) – Tsyvarev Commented Mar 10 at 22:04
  • @Tsyvarev I don't think that was their actual problem. There were multiple obvious copy/paste problems like that. Their real problem likely is around T(&)[N] not being a copyable value-type in the first place. See my answer for some other gotchas hidden in the question :) – sehe Commented Mar 10 at 22:46
  • 1 @sehe: What problem could be "more actual" than missing a function declaration? If showing binary_search.cpp file instead of correct binary_search.hpp is a "copy/paste problem", then why you have described it in your answer instead of fixing the question post itself? – Tsyvarev Commented Mar 11 at 0:11
  • @Tsyvarev like I said, I invite you to read my answer. I haven't "fixed the question". I answered it. Welcome to SO? – sehe Commented Mar 11 at 1:08
  • 1 @sehe: What SO you are talking about? The one where everyday questions about templates placed in the source files are immediately closed as a duplicate e.g. for that question, which explains things in many details? Or do you mean SO where questions without clear error message and many errors in the code are answered with numerous bullets in the answer post? – Tsyvarev Commented Mar 11 at 1:17
 |  Show 3 more comments

1 Answer 1

Reset to default 1

Apparently the element types need to be movable, which isn't true for T(&)[N]. You could replace it with std::array.

Next you will find that you need to make the type printable.

Aside from this, there are numerous issues with the code:

  1. binary_search.cpp defines a template. Its definition MUST be available at place of invocation, so it has to be an include.

    I'd suggest renaming binary_search.cpp to binary_search.hpp or to split into a header/source where all template definitions reside in the header (Why can templates only be implemented in the header file?)

  2. binary_search collides with a standard library algorithm name. This can (will!) lead to surprises when you least expect it (e.g. if you pass the std::array<> directly, ADL will find std::binary_search in favor of your own.

    I suggest renaming your algorithm to my_binary_search or making it a member of a namespace so you can fully qualify it for clarity

  3. Your interface takes T A[] which is equivalent to T* A. Also, it's an antipattern in C++ to pass raw pointers with separate size. Consider specifying it as std::span<T> instead with no overhead. Now you don't have to do the awkward, error-prone copy to a temporary array either

  4. Your interface hard-codes uint8_t for len. This is a recipe for truncation/conversion errors. Worse, passing -1 will silently behave as if len==255 was passed.

  5. You used BOOST_ASSERT which is not part of the Boost Test library, and compiles away in release build (when NDEBUG is defined). You probably wanted BOOST_CHECK or, indeed, BOOST_CHECK_EQUAL here. If you compile with warnings enabled you will actually be informed of mistakes like these (because obs and exp were never used in release mode).

  6. you were missing BOOST_AUTO_TEST_SUITE_END()

  7. you should probably supply link-dependencies instead of manually specifying boost include-directories in cmake:

    target_link_libraries(test_binary_search Boost::unit_test_framework)
    

    Since you know you don't need runtime link dependencies, you can use the headers target to just get the includes:

    target_link_libraries(test_binary_search Boost::headers)
    
  8. also, using target-specific flags/libraries etc. obviates the need for include_directories

  9. on my Boost version (a develop version after 1.88) there are deprecated header warnings, so I'll define BOOST_ALLOW_DEPRECATED_HEADERS

Given all these, here's my counter-proposition:

  • File src/binary_search.cpp

    #include "binary_search.hpp"
    
    // implementations of non-template functions here
    
  • File src/binary_search.hpp

    #pragma once
    #include <cstdint>
    
    namespace mylib {
        template <typename T> T my_binary_search(T A[], uint8_t len, T nu) {
            uint8_t low  = 0;
            uint8_t high = len - 1;
    
            while (low <= high) {
                uint8_t mid = low + (high - low) / 2;
    
                if (nu == A[mid])
                    return mid;
                else if (nu > A[mid])
                    low = mid + 1;
                else
                    high = mid - 1;
            }
    
            return -1;
        }
    } // namespace mylib
    
    #include <span>
    namespace mylib {
        template <typename T, std::size_t N, typename V> //
        T sehe_binary_search(std::span<T, N> a, V const& nu) {
            uint8_t low  = 0;
            uint8_t high = a.size() - 1;
    
            while (low <= high) {
                uint8_t mid = low + (high - low) / 2;
    
                if (nu == a[mid])
                    return mid;
                else if (nu > a[mid])
                    low = mid + 1;
                else
                    high = mid - 1;
            }
    
            return -1;
        }
    } // namespace mylib
    
  • File test/test_binary_search.cpp

    #define BOOST_TEST_MODULE test_algorithms
    #include "../src/binary_search.hpp"
    #include <boost/test/data/test_case.hpp>
    #include <boost/test/included/unit_test.hpp>
    #include <boost/test/included/unit_test_framework.hpp>
    #include <boost/test/tools/output_test_stream.hpp>
    #include <cstdint>
    
    namespace utf = boost::unit_test;
    using Arr8 = std::array<int32_t, 8>;
    
    // make streamable for boost test:
    namespace std {
        std::ostream& boost_test_print_type(std::ostream& os, Arr8 const& arr) {
            os << "{";
            for (char const* sep = ""; auto const& el : arr)
                os << std::exchange(sep, ", ") << el;
            return os << "}";
        }
    } // namespace std
    
    BOOST_AUTO_TEST_SUITE(test_search)
    
    namespace { // file static visibility
        std::array<Arr8, 4> constexpr input_arrs{{
            {26, 31, 41, 41, 58, 59, 101, 104},
            {26, 31, 41, 41, 58, 59, 101, 104},
            {1, 4, 5, 7, 19, 28, 45, 92},
            {1, 4, 5, 7, 19, 28, 45, 92},
        }};
        int32_t constexpr input_vals[]    = {31, 32, 92, 101};
        int32_t constexpr expected_vals[] = {1, -1, 7, -1};
    
        auto test_cases =                 //
            utf::data::make(input_arrs) ^ //
            utf::data::make(input_vals) ^ //
            utf::data::make(expected_vals);
    } // namespace
    
    BOOST_DATA_TEST_CASE(my_binary_search, test_cases, input_arr, nu, exp) {
        int32_t tmp[8];
        std::copy(std::begin(input_arr), std::end(input_arr), std::begin(tmp));
        auto obs = mylib::my_binary_search(tmp, 8, nu);
    
        BOOST_CHECK_EQUAL(obs, exp);
    }
    
    
    BOOST_DATA_TEST_CASE(sehe_binary_search, test_cases, input_arr, nu, exp) {
        auto obs = mylib::sehe_binary_search(std::span(input_arr), nu);
        BOOST_CHECK_EQUAL(obs, exp);
    }
    
    BOOST_AUTO_TEST_SUITE_END()
    
  • File test/CMakeLists.txt

    cmake_minimum_required(VERSION 3.16)
    set(CMAKE_EXPORT_COMPILE_COMMANDS On)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++20 -pedantic -Wall -Wextra -Werror")
    project(test_algorithms)
    
    set(SOURCES 
        ../src/binary_search.cpp
        test_binary_search.cpp
    )
    
    find_package(Boost 1.87.0 REQUIRED COMPONENTS unit_test_framework)
    if (Boost_FOUND)
        enable_testing()
    
        add_executable(test_binary_search ${SOURCES})
        target_link_libraries(test_binary_search Boost::headers)
        target_compile_definitions(test_binary_search PRIVATE BOOST_ALLOW_DEPRECATED_HEADERS)
        add_test(NAME test_binary_search COMMAND test_binary_search)
    endif()
    

With a live demo:

本文标签: cUnable to Compile Parameterized Boost TestsStack Overflow