Fix MSVC compilation errors
Disable the definition of `min`/`max` macros by defining `NOMINMAX`
and prevent macro substitution in the public interface.
Also, quiet floating-point comparisons are defined as template functions
by the MSVC STL which causes compilation errors due to ambiguities in
resolving the template parameter types.
Fixes #668
Fixes #716
Fixes #718
Change-Id: I5fe7832a6a3a7ad0421a2557527528c34b88e9c7
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c218882..4fac3ba 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -444,7 +444,8 @@
set_ceres_threading_model("${CERES_THREADING_MODEL}")
if (BUILD_BENCHMARKS)
- find_package(benchmark QUIET)
+ # Version 1.3 was first to provide import targets
+ find_package(benchmark 1.3 QUIET)
if (benchmark_FOUND)
message("-- Found Google benchmark library. Building Ceres benchmarks.")
else()
@@ -612,6 +613,15 @@
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:-Wno-deprecated-declarations>
$<$<CXX_COMPILER_ID:MSVC>:/wd4996>)
+if (CMAKE_VERSION VERSION_LESS 3.12)
+ # Disable the definiton of min/max macros within the project
+ if (WIN32)
+ add_definitions (-DNOMINMAX)
+ endif (WIN32)
+else (CMAKE_VERSION VERSION_LESS 3.12)
+ add_compile_definitions($<$<BOOL:${WIN32}>:NOMINMAX>)
+endif (CMAKE_VERSION VERSION_LESS 3.12)
+
# Configure the Ceres config.h compile options header using the current
# compile options and put the configured header into the Ceres build
# directory. Note that the ceres/internal subdir in <build>/config where
diff --git a/include/ceres/cubic_interpolation.h b/include/ceres/cubic_interpolation.h
index 134bad2..3ca6b11 100644
--- a/include/ceres/cubic_interpolation.h
+++ b/include/ceres/cubic_interpolation.h
@@ -191,7 +191,7 @@
}
EIGEN_STRONG_INLINE void GetValue(const int n, double* f) const {
- const int idx = std::min(std::max(begin_, n), end_ - 1) - begin_;
+ const int idx = (std::min)((std::max)(begin_, n), end_ - 1) - begin_;
if (kInterleaved) {
for (int i = 0; i < kDataDimension; ++i) {
f[i] = static_cast<double>(data_[kDataDimension * idx + i]);
@@ -402,9 +402,9 @@
EIGEN_STRONG_INLINE void GetValue(const int r, const int c, double* f) const {
const int row_idx =
- std::min(std::max(row_begin_, r), row_end_ - 1) - row_begin_;
+ (std::min)((std::max)(row_begin_, r), row_end_ - 1) - row_begin_;
const int col_idx =
- std::min(std::max(col_begin_, c), col_end_ - 1) - col_begin_;
+ (std::min)((std::max)(col_begin_, c), col_end_ - 1) - col_begin_;
const int n = (kRowMajor) ? num_cols_ * row_idx + col_idx
: num_rows_ * col_idx + row_idx;
diff --git a/include/ceres/internal/householder_vector.h b/include/ceres/internal/householder_vector.h
index e5fc9bf..7700208 100644
--- a/include/ceres/internal/householder_vector.h
+++ b/include/ceres/internal/householder_vector.h
@@ -82,11 +82,11 @@
v->head(v->rows() - 1) /= v_pivot;
}
-template <typename XVectorType, typename Scalar, int N>
-Eigen::Matrix<Scalar, N, 1> ApplyHouseholderVector(
+template <typename XVectorType, typename Derived>
+typename Derived::PlainObject ApplyHouseholderVector(
const XVectorType& y,
- const Eigen::Matrix<Scalar, N, 1>& v,
- const Scalar& beta) {
+ const Eigen::MatrixBase<Derived>& v,
+ const typename Derived::Scalar& beta) {
return (y - v * (beta * (v.transpose() * y)));
}
diff --git a/include/ceres/internal/numeric_diff.h b/include/ceres/internal/numeric_diff.h
index c44733f..351845c 100644
--- a/include/ceres/internal/numeric_diff.h
+++ b/include/ceres/internal/numeric_diff.h
@@ -121,7 +121,7 @@
// thus ridders_relative_initial_step_size is used.
if (kMethod == RIDDERS) {
min_step_size =
- std::max(min_step_size, options.ridders_relative_initial_step_size);
+ (std::max)(min_step_size, options.ridders_relative_initial_step_size);
}
// For each parameter in the parameter block, use finite differences to
@@ -132,7 +132,7 @@
num_residuals_internal);
for (int j = 0; j < parameter_block_size_internal; ++j) {
- const double delta = std::max(min_step_size, step_size(j));
+ const double delta = (std::max)(min_step_size, step_size(j));
if (kMethod == RIDDERS) {
if (!EvaluateRiddersJacobianColumn(functor,
@@ -296,7 +296,7 @@
// norm_error is supposed to decrease as the finite difference tableau
// generation progresses, serving both as an estimate for differentiation
// error and as a measure of differentiation numerical stability.
- double norm_error = std::numeric_limits<double>::max();
+ double norm_error = (std::numeric_limits<double>::max)();
// Loop over decreasing step sizes until:
// 1. Error is smaller than a given value (ridders_epsilon),
@@ -342,7 +342,7 @@
options.ridders_step_shrink_factor;
// Compute the difference between the previous value and the current.
- double candidate_error = std::max(
+ double candidate_error = (std::max)(
(current_candidates->col(k) - current_candidates->col(k - 1))
.norm(),
(current_candidates->col(k) - previous_candidates->col(k - 1))
diff --git a/include/ceres/internal/port.h b/include/ceres/internal/port.h
index 2bc44f4..4275b0e 100644
--- a/include/ceres/internal/port.h
+++ b/include/ceres/internal/port.h
@@ -63,4 +63,26 @@
// 202002L)
#endif // !defined(CERES_HAS_CPP20)
+// Prevents symbols from being substituted by the corresponding macro definition
+// under the same name. For instance, min and max are defined as macros on
+// Windows (unless NOMINMAX is defined) which causes compilation errors when
+// defining or referencing symbols under the same name.
+//
+// To be robust in all cases particularly when NOMINMAX cannot be used, use this
+// macro to annotate min/max declarations/definitions. Examples:
+//
+// int max CERES_PREVENT_MACRO_SUBSTITUTION();
+// min CERES_PREVENT_MACRO_SUBSTITUTION(a, b);
+// max CERES_PREVENT_MACRO_SUBSTITUTION(a, b);
+//
+// NOTE: In case the symbols for which the substitution must be prevented are
+// used within another macro, the substitution must be inhibited using parens as
+//
+// (std::numerical_limits<double>::max)()
+//
+// since the helper macro will not work here. Do not use this technique in
+// general case, because it will prevent argument-dependent lookup (ADL).
+//
+#define CERES_PREVENT_MACRO_SUBSTITUTION // Yes, it's empty
+
#endif // CERES_PUBLIC_INTERNAL_PORT_H_
diff --git a/include/ceres/jet.h b/include/ceres/jet.h
index 10ce51e..fba1e2a 100644
--- a/include/ceres/jet.h
+++ b/include/ceres/jet.h
@@ -443,15 +443,9 @@
using std::fpclassify;
using std::hypot;
using std::isfinite;
-using std::isgreater;
-using std::isgreaterequal;
using std::isinf;
-using std::isless;
-using std::islessequal;
-using std::islessgreater;
using std::isnan;
using std::isnormal;
-using std::isunordered;
using std::log;
using std::log10;
using std::log1p;
@@ -465,6 +459,47 @@
using std::tan;
using std::tanh;
+// MSVC (up to 1930) defines quiet comparison functions as template functions
+// which causes compilation errors due to ambiguity in the template parameter
+// type resolution for using declarations in the ceres namespace. Workaround the
+// issue by defining specific overload and bypass MSVC standard library
+// definitions.
+#if defined(_MSC_VER)
+inline bool isgreater(double lhs,
+ double rhs) noexcept(noexcept(std::isgreater(lhs, rhs))) {
+ return std::isgreater(lhs, rhs);
+}
+inline bool isless(double lhs,
+ double rhs) noexcept(noexcept(std::isless(lhs, rhs))) {
+ return std::isless(lhs, rhs);
+}
+inline bool islessequal(double lhs,
+ double rhs) noexcept(noexcept(std::islessequal(lhs,
+ rhs))) {
+ return std::islessequal(lhs, rhs);
+}
+inline bool isgreaterequal(double lhs, double rhs) noexcept(
+ noexcept(std::isgreaterequal(lhs, rhs))) {
+ return std::isgreaterequal(lhs, rhs);
+}
+inline bool islessgreater(double lhs, double rhs) noexcept(
+ noexcept(std::islessgreater(lhs, rhs))) {
+ return std::islessgreater(lhs, rhs);
+}
+inline bool isunordered(double lhs,
+ double rhs) noexcept(noexcept(std::isunordered(lhs,
+ rhs))) {
+ return std::isunordered(lhs, rhs);
+}
+#else
+using std::isgreater;
+using std::isgreaterequal;
+using std::isless;
+using std::islessequal;
+using std::islessgreater;
+using std::isunordered;
+#endif
+
#ifdef CERES_HAS_CPP20
using std::lerp;
using std::midpoint;
@@ -1246,8 +1281,9 @@
static constexpr bool tinyness_before =
std::numeric_limits<T>::tinyness_before;
- static constexpr ceres::Jet<T, N> min() noexcept {
- return ceres::Jet<T, N>(std::numeric_limits<T>::min());
+ static constexpr ceres::Jet<T, N> min
+ CERES_PREVENT_MACRO_SUBSTITUTION() noexcept {
+ return ceres::Jet<T, N>((std::numeric_limits<T>::min)());
}
static constexpr ceres::Jet<T, N> lowest() noexcept {
return ceres::Jet<T, N>(std::numeric_limits<T>::lowest());
@@ -1271,8 +1307,9 @@
return ceres::Jet<T, N>(std::numeric_limits<T>::denorm_min());
}
- static constexpr ceres::Jet<T, N> max() noexcept {
- return ceres::Jet<T, N>(std::numeric_limits<T>::max());
+ static constexpr ceres::Jet<T, N> max
+ CERES_PREVENT_MACRO_SUBSTITUTION() noexcept {
+ return ceres::Jet<T, N>((std::numeric_limits<T>::max)());
}
};
@@ -1326,8 +1363,8 @@
};
};
- static inline Real highest() { return Real(std::numeric_limits<T>::max()); }
- static inline Real lowest() { return Real(-std::numeric_limits<T>::max()); }
+ static inline Real highest() { return Real((std::numeric_limits<T>::max)()); }
+ static inline Real lowest() { return Real(-(std::numeric_limits<T>::max)()); }
};
// Specifying the return type of binary operations between Jets and scalar types
diff --git a/include/ceres/manifold.h b/include/ceres/manifold.h
index 14a5792..458eec0 100644
--- a/include/ceres/manifold.h
+++ b/include/ceres/manifold.h
@@ -285,6 +285,9 @@
public:
ProductManifold(const ProductManifold&) = delete;
ProductManifold& operator=(const ProductManifold&) = delete;
+ // NOTE: Do not remove the trivial destructor as this will cause linker errors
+ // in MSVC builds.
+ ~ProductManifold() override;
// NOTE: The constructor takes ownership of the input
// manifolds.
@@ -307,7 +310,7 @@
ManifoldPtr& manifold = manifolds_[i];
manifold = std::move(manifolds_array[i]);
- buffer_size_ = std::max(
+ buffer_size_ = (std::max)(
buffer_size_, manifold->TangentSize() * manifold->AmbientSize());
ambient_size_ += manifold->AmbientSize();
tangent_size_ += manifold->TangentSize();
diff --git a/include/ceres/tiny_solver.h b/include/ceres/tiny_solver.h
index 16fd2a8..86a655d 100644
--- a/include/ceres/tiny_solver.h
+++ b/include/ceres/tiny_solver.h
@@ -250,7 +250,7 @@
const Scalar max_diagonal = 1e32;
for (int i = 0; i < lm_diagonal_.rows(); ++i) {
lm_diagonal_[i] = std::sqrt(
- u * std::min(std::max(jtj_(i, i), min_diagonal), max_diagonal));
+ u * (std::min)((std::max)(jtj_(i, i), min_diagonal), max_diagonal));
jtj_regularized_(i, i) += lm_diagonal_[i] * lm_diagonal_[i];
}
@@ -306,7 +306,7 @@
}
Scalar tmp = Scalar(2 * rho - 1);
- u = u * std::max(Scalar(1 / 3.), 1 - tmp * tmp * tmp);
+ u = u * (std::max)(Scalar(1 / 3.), Scalar(1) - tmp * tmp * tmp);
v = 2;
} else {
diff --git a/internal/ceres/CMakeLists.txt b/internal/ceres/CMakeLists.txt
index ea0bed8..fa538fe 100644
--- a/internal/ceres/CMakeLists.txt
+++ b/internal/ceres/CMakeLists.txt
@@ -287,7 +287,7 @@
# CERES_STATIC_DEFINE is generated by the GenerateExportHeader CMake module
# used to autogerate export.h. The macro should not be renamed without
# updating the corresponding generate_export_header invocation.
- target_compile_definitions(ceres_static PRIVATE CERES_STATIC_DEFINE)
+ target_compile_definitions(ceres_static PUBLIC CERES_STATIC_DEFINE)
else()
# In a static library build, not additional access layer is necessary as all
# symbols are visible.
@@ -399,14 +399,6 @@
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
if (BUILD_TESTING AND GFLAGS)
- add_library(test_util STATIC
- evaluator_test_utils.cc
- numeric_diff_test_utils.cc
- test_util.cc)
-
- target_include_directories(test_util PUBLIC ${Ceres_SOURCE_DIR}/internal)
- target_link_libraries (test_util PUBLIC ceres_static gflags)
-
add_library(gtest gmock_gtest_all.cc gmock_main.cc)
if (BUILD_SHARED_LIBS)
# Define gtest-specific shared library flags for compilation.
@@ -417,12 +409,21 @@
target_compile_definitions(gtest
PRIVATE GTEST_CREATE_SHARED_LIBRARY=1
GFLAGS_DLL_DEFINE_FLAG=GTEST_API_
- INTERFACE GTEST_LINKED_AS_SHARED_LIBRARY=1)
+ INTERFACE GTEST_LINKED_AS_SHARED_LIBRARY=1
+ GFLAGS_DLL_DECLARE_FLAG=GTEST_API_)
endif()
target_include_directories(gtest PRIVATE ${Ceres_SOURCE_DIR}/internal/ceres)
target_link_libraries(gtest PRIVATE Ceres::ceres gflags)
+ add_library(test_util STATIC
+ evaluator_test_utils.cc
+ numeric_diff_test_utils.cc
+ test_util.cc)
+
+ target_include_directories(test_util PUBLIC ${Ceres_SOURCE_DIR}/internal)
+ target_link_libraries (test_util PUBLIC ceres_static gflags gtest)
+
macro (CERES_TEST NAME)
add_executable(${NAME}_test ${NAME}_test.cc)
# Pull in local headers from the generated test directories when ceres_test()
diff --git a/internal/ceres/autodiff_benchmarks/CMakeLists.txt b/internal/ceres/autodiff_benchmarks/CMakeLists.txt
index 610ebc3..99af152 100644
--- a/internal/ceres/autodiff_benchmarks/CMakeLists.txt
+++ b/internal/ceres/autodiff_benchmarks/CMakeLists.txt
@@ -9,9 +9,3 @@
add_executable(autodiff_benchmarks autodiff_benchmarks.cc)
add_dependencies_to_benchmark(autodiff_benchmarks)
target_compile_options(autodiff_benchmarks PRIVATE ${CERES_BENCHMARK_FLAGS})
-
-# All other flags + fast-math
-list(APPEND CERES_BENCHMARK_FAST_MATH_FLAGS ${CERES_BENCHMARK_FLAGS} "-ffast-math")
-add_executable(autodiff_benchmarks_fast_math autodiff_benchmarks.cc)
-add_dependencies_to_benchmark(autodiff_benchmarks_fast_math)
-target_compile_options(autodiff_benchmarks_fast_math PRIVATE ${CERES_BENCHMARK_FAST_MATH_FLAGS})
diff --git a/internal/ceres/dense_cholesky_test.cc b/internal/ceres/dense_cholesky_test.cc
index 0e84207..c5189b8 100644
--- a/internal/ceres/dense_cholesky_test.cc
+++ b/internal/ceres/dense_cholesky_test.cc
@@ -97,19 +97,25 @@
}
}
-INSTANTIATE_TEST_SUITE_P(_,
- DenseCholeskyTest,
- ::testing::Values(EIGEN
+namespace {
+
+// NOTE: preprocessor directives in a macro are not standard conforming
+decltype(auto) MakeValues() {
+ return ::testing::Values(EIGEN
#ifndef CERES_NO_LAPACK
- ,
- LAPACK
+ ,
+ LAPACK
#endif
#ifndef CERES_NO_CUDA
- ,
- CUDA
+ ,
+ CUDA
#endif
- ),
- ParamInfoToString);
+ );
+}
+
+} // namespace
+
+INSTANTIATE_TEST_SUITE_P(_, DenseCholeskyTest, MakeValues(), ParamInfoToString);
} // namespace internal
} // namespace ceres
diff --git a/internal/ceres/jet_test.cc b/internal/ceres/jet_test.cc
index f06c599..6a76e1d 100644
--- a/internal/ceres/jet_test.cc
+++ b/internal/ceres/jet_test.cc
@@ -30,7 +30,11 @@
// The floating-point environment access and modification is only meaningful
// with the following pragma.
+#ifdef _MSC_VER
+#pragma fenv_access(on)
+#else
#pragma STDC FENV_ACCESS ON
+#endif
#include "ceres/jet.h"
diff --git a/internal/ceres/levenberg_marquardt_strategy_test.cc b/internal/ceres/levenberg_marquardt_strategy_test.cc
index 0e7ec8d..86d35a2 100644
--- a/internal/ceres/levenberg_marquardt_strategy_test.cc
+++ b/internal/ceres/levenberg_marquardt_strategy_test.cc
@@ -148,7 +148,7 @@
// are versions of glog which are not in the google namespace.
using namespace google;
-#if defined(_MSC_VER)
+#if defined(GLOG_NO_ABBREVIATED_SEVERITIES)
// Use GLOG_WARNING to support MSVC if GLOG_NO_ABBREVIATED_SEVERITIES
// is defined.
EXPECT_CALL(log,
diff --git a/internal/ceres/manifold.cc b/internal/ceres/manifold.cc
index 451286a..1f4781e 100644
--- a/internal/ceres/manifold.cc
+++ b/internal/ceres/manifold.cc
@@ -312,6 +312,20 @@
return true;
}
+// While the trivial destructor is not necessary, MSVC (1930) complains in
+// shared library builds by issuing a linker error:
+//
+// bundle_adjuster.obj : error LNK2019: unresolved external symbol
+// "__declspec(dllimport) const ceres::ProductManifold::`vftable'"
+// (__imp_??_7ProductManifold@ceres@@6B@) referenced in function "public:
+// __cdecl ceres::ProductManifold::ProductManifold<class
+// ceres::QuaternionManifold,class ceres::EuclideanManifold>(class
+// ceres::QuaternionManifold *,class ceres::EuclideanManifold *)"
+// (??$?0VQuaternionManifold@ceres@@VEuclideanManifold@1@@ProductManifold@ceres@@QEAA@PEAVQuaternionManifold@1@PEAVEuclideanManifold@1@@Z)
+//
+// Providing a trivial implementation here resolves the issue.
+ProductManifold::~ProductManifold() = default;
+
int ProductManifold::AmbientSize() const { return ambient_size_; }
int ProductManifold::TangentSize() const { return tangent_size_; }
diff --git a/internal/ceres/miniglog/glog/logging.h b/internal/ceres/miniglog/glog/logging.h
index f03c914..5604bd4 100644
--- a/internal/ceres/miniglog/glog/logging.h
+++ b/internal/ceres/miniglog/glog/logging.h
@@ -132,7 +132,7 @@
// added, all log output is also sent to each sink through the send function.
// In this implementation, WaitTillSent() is called immediately after the send.
// This implementation is not thread safe.
-class CERES_NO_EXPORT LogSink {
+class CERES_EXPORT LogSink {
public:
virtual ~LogSink() = default;
virtual void send(LogSeverity severity,
@@ -146,7 +146,7 @@
};
// Global set of log sinks. The actual object is defined in logging.cc.
-extern CERES_NO_EXPORT std::set<LogSink*> log_sinks_global;
+extern CERES_EXPORT std::set<LogSink*> log_sinks_global;
inline void InitGoogleLogging(const char* /* argv */) {
// Do nothing; this is ignored.
@@ -169,7 +169,7 @@
// defined, output is directed to std::cerr. This class should not
// be directly instantiated in code, rather it should be invoked through the
// use of the log macros LG, LOG, or VLOG.
-class CERES_NO_EXPORT MessageLogger {
+class CERES_EXPORT MessageLogger {
public:
MessageLogger(const char* file, int line, const char* tag, int severity)
: file_(file), line_(line), tag_(tag), severity_(severity) {
@@ -288,7 +288,7 @@
// This class is used to explicitly ignore values in the conditional
// logging macros. This avoids compiler warnings like "value computed
// is not used" and "statement has no effect".
-class CERES_NO_EXPORT LoggerVoidify {
+class CERES_EXPORT LoggerVoidify {
public:
// This has to be an operator with a precedence lower than << but
// higher than ?:
diff --git a/internal/ceres/rotation_test.cc b/internal/ceres/rotation_test.cc
index d97e385..d6de650 100644
--- a/internal/ceres/rotation_test.cc
+++ b/internal/ceres/rotation_test.cc
@@ -88,27 +88,29 @@
MATCHER_P(IsNearQuaternion, expected, "") {
// Quaternions are equivalent upto a sign change. So we will compare
// both signs before declaring failure.
- bool near = true;
+ bool is_near = true;
+ // NOTE: near (and far) can be defined as macros on the Windows platform (for
+ // ancient pascal calling convention). Do not use these identifiers.
for (int i = 0; i < 4; i++) {
if (fabs(arg[i] - expected[i]) > kTolerance) {
- near = false;
+ is_near = false;
break;
}
}
- if (near) {
+ if (is_near) {
return true;
}
- near = true;
+ is_near = true;
for (int i = 0; i < 4; i++) {
if (fabs(arg[i] + expected[i]) > kTolerance) {
- near = false;
+ is_near = false;
break;
}
}
- if (near) {
+ if (is_near) {
return true;
}