Modify

Ticket #11149 (closed Bugs: wontfix)

Opened 2 years ago

Last modified 15 months ago

boost::multiprecision compilation fail with std::max

Reported by: minorlogic@… Owned by: johnmaddock
Milestone: To Be Determined Component: multiprecision
Version: Boost 1.57.0 Severity: Showstopper
Keywords: Cc:

Description

pseudo code to reproduce:

typedef boost::multiprecision::number<boost::multiprecision::cpp_dec_float<50> > cpp_dec_float_50; using std::max; cpp_dec_float_50 x, y=max(x, 2*x);

Fails to compile

Attachments

Change History

comment:1 Changed 2 years ago by johnmaddock

  • Status changed from new to closed
  • Resolution set to wontfix

This is to be expected, and is a side effect of returning expression templates from arithmetic operators, as mentioned in the introduction (http://www.boost.org/doc/libs/1_57_0/libs/multiprecision/doc/html/boost_multiprecision/intro.html#boost_multiprecision.intro.expression_templates) template argument type deduction may fail when passing the result of an expression to a template function.

The workarounds are either:

  • Use a type with expression templates turned off - in your case that would be number<cpp_dec_float_50::backend_type, et_off>
  • Explicitly specify the template argumnent type for the call - so std::max<cpp_dec_float_50>(x, 2*x), or:
  • Cast expressions prior to the call: std::max(x, static_cast<cpp_dec_float_50>(2*x)).

I realise that the last 2 may not be possible if you don't control the code where the issue occurs, and that the first may have a performance impact (much lessened in C++11 though with move-semantics), but the only other alternative I can think of is to overload std::max - but that's not permitted by a strict reading of the standard I believe.

Closing for now.... if a lot of folks fall into this trap I may reconsider

comment:2 Changed 2 years ago by chtz@…

Just some notes: First of all a crossreference to original issue: http://eigen.tuxfamily.org/bz/show_bug.cgi?id=982

About overloading std::max: That is indeed (at least) bad style, however, you can very well implement a max/min function in your own namespace. Then the following would be possible (due to ADL):

  using std::max;
  cpp_dec_float_50 x, y = max(x, 2*x);

Unfortunately, ADL does not work when writing (max) instead of max, which we originally did in Eigen (we replaced this by another mechanism meanwhile).

I guess, we'll need to adapt our add <Scalar> arguments to our min/max-calls anyways, if we want to support ET-types.

comment:3 Changed 2 years ago by johnmaddock

I'm quite happy to add min/max to boost::multiprecision namespace, I didn't because my gut feeling was that folks wouldn't be relying on ADL to find those (why would they? The std:: ones are perfectly satisfactory). And as you say, for compilers that that define min/max as macros, the needed ()'s break ADL lookup.

I'm curious, what's the other mechanism you used in this case?

comment:4 Changed 2 years ago by chtz@…

std::max/min are satisfactory, except for the fact that they don't accept ETs. Also, if they happen to be called with matching ETs, you have to evaluate both expressions (which you have to, anyways -- unless you have an extra clever operator< overload), but the result will be evaluated once more.

In Eigen we encapsulated max/min to maxi/mini functions in a separate namespace. They are implemented using ADL with an empty macro preventing max()/min() macro expansions. We require ADL, since we support some scalar types which define max and min in their own namespace.

I guess, you also define functions such as sqrt in your own namespace, don't you?

comment:4 Changed 2 years ago by chtz@…

std::max/min are satisfactory, except for the fact that they don't accept ETs. Also, if they happen to be called with matching ETs, you have to evaluate both expressions (which you have to, anyways -- unless you have an extra clever operator< overload), but the result will be evaluated once more.

In Eigen we encapsulated max/min to maxi/mini functions in a separate namespace. They are implemented using ADL with an empty macro preventing max()/min() macro expansions. We require ADL, since we support some scalar types which define max and min in their own namespace.

I guess, you also define functions such as sqrt in your own namespace, don't you?

comment:5 Changed 15 months ago by dbrake@…

@johnmaddock, I got bit by this bug, too. was there a resolution? i'd prefer to keep et_on if possible.

comment:6 follow-ups: ↓ 8 ↓ 10 Changed 15 months ago by johnmaddock

If you add the following before including the Eigen headers does it fix things?

namespace boost { namespace multiprecision {

   template <class Backend, class tag, class A1, class A2, class A3, class A4> 
   inline number<Backend, et_on> min(const number<Backend, et_on>& arg, const detail::expression<tag, A1, A2, A3, A4>& a)
   {
      number<Backend, et_on> t(a);
      return (std::min)(arg, t);
   }
   template <class tag, class A1, class A2, class A3, class A4, class Backend> 
   inline number<Backend, et_on> min(const detail::expression<tag, A1, A2, A3, A4>& arg, const number<Backend, et_on>& a)
   {
      number<Backend, et_on> t(arg);
      return (std::min)(arg, a);
   }
   template <class tag, class A1, class A2, class A3, class A4, class tagb, class A1b, class A2b, class A3b, class A4b> 
   inline typename detail::expression<tag, A1, A2, A3, A4>::result_type min(const detail::expression<tag, A1, A2, A3, A4>& arg, const detail::expression<tagb, A1b, A2b, A3b, A4b>& a)
   {
      number<Backend, et_on> t1(arg), t2(a);
      return (std::min)(arg, a);
   }

   template <class Backend, class tag, class A1, class A2, class A3, class A4>
   inline number<Backend, et_on> max(const number<Backend, et_on>& arg, const detail::expression<tag, A1, A2, A3, A4>& a)
   {
      number<Backend, et_on> t(a);
      return (std::max)(arg, t);
   }
   template <class tag, class A1, class A2, class A3, class A4, class Backend>
   inline number<Backend, et_on> max(const detail::expression<tag, A1, A2, A3, A4>& arg, const number<Backend, et_on>& a)
   {
      number<Backend, et_on> t(arg);
      return (std::max)(arg, a);
   }
   template <class tag, class A1, class A2, class A3, class A4, class tagb, class A1b, class A2b, class A3b, class A4b>
   inline typename detail::expression<tag, A1, A2, A3, A4>::result_type max(const detail::expression<tag, A1, A2, A3, A4>& arg, const detail::expression<tagb, A1b, A2b, A3b, A4b>& a)
   {
      number<Backend, et_on> t1(arg), t2(a);
      return (std::max)(arg, a);
   }

} }

comment:7 follow-up: ↓ 9 Changed 15 months ago by chtz@…

Just FYI: We worked around the issue in the development branch of Eigen (see discussion of our bug entry). I guess the solution in comment 6 would have worked as well (after changing some of the arguments). You could end up with some unecessary copies however -- though these should usually be optimized away by RVO.

comment:8 in reply to: ↑ 6 Changed 15 months ago by dbrake@…

Replying to johnmaddock: thanks for the quick reply, john. i tried the code, and couldn't get off the ground. the two templates of type

template <class tag, class A1, class A2, class A3, class A4, class tagb, class A1b, class A2b, class A3b, class A4b>

and

template <class tag, class A1, class A2, class A3, class A4, class tagb, class A1b, class A2b, class A3b, class A4b>

(third and last) have undefined identifier Backend.

comment:9 in reply to: ↑ 7 Changed 15 months ago by dbrake@…

Replying to chtz@…:

Just FYI: We worked around the issue in the development branch of Eigen (see discussion of our bug entry). I guess the solution in comment 6 would have worked as well (after changing some of the arguments). You could end up with some unecessary copies however -- though these should usually be optimized away by RVO.

Thanks for the quick reply, chtz. I get to support older versions of Eigen with 6 (John's solution), but just have to wait for Eigen develop to become release to get the official solution. Which do you recommend as a solution? I think I prefer 6 for immediate satisfaction, but would love an opinion.

comment:10 in reply to: ↑ 6 Changed 15 months ago by dbrake@…

Replying to johnmaddock:

hi john, i replaced the number<Backend, et_on> with the deduced type similar to the return type from those two templates:

namespace boost { namespace multiprecision {

template <class Backend, class tag, class A1, class A2, class A3, class A4> 
	inline number<Backend, et_on> min(const number<Backend, et_on>& arg, const detail::expression<tag, A1, A2, A3, A4>& a)
	{
		number<Backend, et_on> t(a);
		return (std::min)(arg, t);
	}
template <class tag, class A1, class A2, class A3, class A4, class Backend> 
	inline number<Backend, et_on> min(const detail::expression<tag, A1, A2, A3, A4>& arg, const number<Backend, et_on>& a)
	{
		number<Backend, et_on> t(arg);
		return (std::min)(arg, a);
	}
template <class tag, class A1, class A2, class A3, class A4, class tagb, class A1b, class A2b, class A3b, class A4b> 
	inline typename detail::expression<tag, A1, A2, A3, A4>::result_type min(const detail::expression<tag, A1, A2, A3, A4>& arg, const detail::expression<tagb, A1b, A2b, A3b, A4b>& a)
	{
		using N = typename detail::expression<tag, A1, A2, A3, A4>::result_type;
		N t1(arg), t2(a);
		return (std::min)(arg, a);
	}

template <class Backend, class tag, class A1, class A2, class A3, class A4>
	inline number<Backend, et_on> max(const number<Backend, et_on>& arg, const detail::expression<tag, A1, A2, A3, A4>& a)
	{
		number<Backend, et_on> t(a);
		return (std::max)(arg, t);
	}
template <class tag, class A1, class A2, class A3, class A4, class Backend>
	inline number<Backend, et_on> max(const detail::expression<tag, A1, A2, A3, A4>& arg, const number<Backend, et_on>& a)
	{
		number<Backend, et_on> t(arg);
		return (std::max)(arg, a);
	}
template <class tag, class A1, class A2, class A3, class A4, class tagb, class A1b, class A2b, class A3b, class A4b>
	inline typename detail::expression<tag, A1, A2, A3, A4>::result_type max(const detail::expression<tag, A1, A2, A3, A4>& arg, const detail::expression<tagb, A1b, A2b, A3b, A4b>& a)
	{	
		using N = typename detail::expression<tag, A1, A2, A3, A4>::result_type;
		N t1(arg), t2(a);
		return (std::max)(arg, a);
	}

} }

These type replacements for these two templates appear ok.

My use of Eigen for JacobiSVD still fails, due to missing max. I am including the file with the above code before #include <Eigen/Core> or any other Eigen files, so I am still puzzled. I will get a minimal working example together to ensure that the rest of my project is not interfering with this solution. Thanks again for the help.

comment:11 follow-ups: ↓ 12 ↓ 13 Changed 15 months ago by johnmaddock

Note that this issue can occur with built-in types too:

   short sa(1), sb(2), sc(3);
   short sd = (std::min)(sa, sb + sc);  // ambiguous

So IMO it is correct to fix this with a typecast at the call site of std::min/max.

Which is not to say I won't add the overloads to namespace boost::multiprecision as well... once I have them correct and move enabled etc...

comment:12 in reply to: ↑ 11 Changed 15 months ago by dbrake@…

Replying to johnmaddock:

it sounds like my best bet then is to use the develop branch of Eigen, or to turn off expression templates until the fix becomes release code?

note that the code you posted did indeed allow me to compute max() in my code, but still not in the context of eigen's svd.

comment:13 in reply to: ↑ 11 Changed 15 months ago by dbrake@…

Replying to johnmaddock:

I have prepared a minimal working example, demonstrating your comment 11, as well as the ongoing problem with Eigen.

// this file is intended to be compiled against boost multiprecision and eigen.
// it demonstrates how eigen fails to find the correct overload of min/max.

// example compile command
// g++ -std=c++11 -I/usr/local/Cellar/eigen/3.2.7/include/eigen3/ -I/usr/local/Cellar/boost/1.58.0/include/ mwe_eigen_mpfr_max.cpp -lmpfr

// this example fails to compile under clang on osx 10.11
// Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
// Apple LLVM version 7.0.2 (clang-700.1.81)
// Target: x86_64-apple-darwin15.3.0
// Thread model: posix

// prepared by Daniel Brake
// University of Notre Dame
// dbrake@nd.edu
// Feb 2016

#include <algorithm>
#include <cmath>
#include <boost/multiprecision/mpfr.hpp>

using mpfr_float = boost::multiprecision::number<boost::multiprecision::mpfr_float_backend<0>, boost::multiprecision::et_on>;  
// changing et_on to et_off allows this code to compile.  
// the problem with min/max is with expression templates.


// from John Maddock, a boost multiprecision author, via email 2016.02.16 and 
// https://svn.boost.org/trac/boost/ticket/11149
namespace boost { namespace multiprecision {

template <class Backend, class tag, class A1, class A2, class A3, class A4> 
	inline number<Backend, et_on> min(const number<Backend, et_on>& arg, const detail::expression<tag, A1, A2, A3, A4>& a)
	{
		number<Backend, et_on> t(a);
		return (std::min)(arg, t);
	}
template <class tag, class A1, class A2, class A3, class A4, class Backend> 
	inline number<Backend, et_on> min(const detail::expression<tag, A1, A2, A3, A4>& arg, const number<Backend, et_on>& a)
	{
		number<Backend, et_on> t(arg);
		return (std::min)(arg, a);
	}
template <class tag, class A1, class A2, class A3, class A4, class tagb, class A1b, class A2b, class A3b, class A4b> 
	inline typename detail::expression<tag, A1, A2, A3, A4>::result_type min(const detail::expression<tag, A1, A2, A3, A4>& arg, const detail::expression<tagb, A1b, A2b, A3b, A4b>& a)
	{
		using N = typename detail::expression<tag, A1, A2, A3, A4>::result_type;
		N t1(arg), t2(a);
		return (std::min)(arg, a);
	}

template <class Backend, class tag, class A1, class A2, class A3, class A4>
	inline number<Backend, et_on> max(const number<Backend, et_on>& arg, const detail::expression<tag, A1, A2, A3, A4>& a)
	{
		number<Backend, et_on> t(a);
		return (std::max)(arg, t);
	}
template <class tag, class A1, class A2, class A3, class A4, class Backend>
	inline number<Backend, et_on> max(const detail::expression<tag, A1, A2, A3, A4>& arg, const number<Backend, et_on>& a)
	{
		number<Backend, et_on> t(arg);
		return (std::max)(arg, a);
	}
template <class tag, class A1, class A2, class A3, class A4, class tagb, class A1b, class A2b, class A3b, class A4b>
	inline typename detail::expression<tag, A1, A2, A3, A4>::result_type max(const detail::expression<tag, A1, A2, A3, A4>& arg, const detail::expression<tagb, A1b, A2b, A3b, A4b>& a)
	{	
		using N = typename detail::expression<tag, A1, A2, A3, A4>::result_type;
		N t1(arg), t2(a);
		return (std::max)(arg, a);
	}

} }


// include AFTER the above templates
#include <Eigen/Dense>

// reopen the Eigen namespace to provide NumTraits for mpfr_float.
namespace Eigen {
	// describe mpfr_float to Eigen
	// permits to get the epsilon, dummy_precision, lowest, highest functions
	template<> struct NumTraits<mpfr_float> : GenericNumTraits<mpfr_float> 
	{
		
		typedef mpfr_float Real;
		typedef mpfr_float NonInteger;
		typedef mpfr_float Nested;
		enum {
			IsComplex = 0,
			IsInteger = 0,
			IsSigned = 1,
			RequireInitialization = 1, // yes, require initialization, otherwise get crashes
			ReadCost = 20,
			AddCost = 30,
			MulCost = 40
		};
		
		
		inline static Real highest() {
			
			return (mpfr_float(1) - epsilon()) * pow(mpfr_float(2),mpfr_get_emax()-1);
		}
		
		inline static Real lowest() {
			return -highest();
		}
		
		inline static Real dummy_precision()
		{
			return pow( mpfr_float(10),-int(mpfr_float::default_precision()-3));
		}
		
		inline static Real epsilon()
		{
			return pow(mpfr_float(10),-int(mpfr_float::default_precision()));
		}
		//http://www.manpagez.com/info/mpfr/mpfr-2.3.2/mpfr_31.php
	};

}


int main()
{
	mpfr_float a(1), b(2), c(3);
	using std::max;
	 // this line works because of the 6 above templates providing min/max.
	auto r = max(a,b*b+c);

	// this line will fail, not finding the call to max.  this is syntactically similar to the failing eigen call from version 3.2.7.
	
	auto s = (max)(a,b*b+c);

	// make a 2x2 dynamic matrix
	Eigen::Matrix<mpfr_float, Eigen::Dynamic, Eigen::Dynamic> A(2,2);
	A << 2, 1, 1, 2; // populate it
	

	// this is the offending line.  Eigen attempts to call max, but cannot see the above templates.
	Eigen::JacobiSVD<Eigen::Matrix<mpfr_float, Eigen::Dynamic, Eigen::Dynamic>> svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV);
	// In file included from mwe_eigen_mpfr_max.cpp:68:
	// In file included from /usr/local/Cellar/eigen/3.2.7/include/eigen3/Eigen/Dense:5:
	// In file included from /usr/local/Cellar/eigen/3.2.7/include/eigen3/Eigen/SVD:24:
	// /usr/local/Cellar/eigen/3.2.7/include/eigen3/Eigen/src/SVD/JacobiSVD.h:876:32: error: no matching function for call to 'max'
	return 0;

}

comment:14 follow-up: ↓ 15 Changed 15 months ago by chtz@…

First of all, in all patches/examples you wrote above, you need to replace the arguments to (std::min)(arg, a); by the appropriate t,t1,t2. For a clean solution you should actually implement min,max similar to the way you implement atan2.

However, that will indeed still not work with Eigen3.2, since (as you noted) we use something like (max)(a,b) which prevents ADL (for whatever reason ...) I don't really like having to fix this in Eigen3.2, but actually the using std::max; ... (max)(A,B) we are currently doing is more or less pointless, here.

comment:15 in reply to: ↑ 14 ; follow-up: ↓ 19 Changed 15 months ago by dbrake@…

Replying to chtz@…:

i checked out the eigen develop 3.3-beta1 code, and confirm that this issue is fixed, with no need for the min/max that John Maddock provided in comment 6. comment 6 does still resolve the problem for min/max'ing with et_on not in the context of eigen.

a workaround for me might consist of providing a compile-time configure option for my library for turning off the expression templates for Boost.Multiprecision. if they are on, require a minimum version of eigen. otherwise, let it ride with any version 3.2 or later. i have an m4 macro for autoconf which finds eigen (does eigen provide such a macro? i didn't find one), and the et_on/off requirement could be written into the call.

any thoughts?

comment:16 Changed 15 months ago by johnmaddock

As noted above, the overloads I gave before are buggy... which is what happens when you try to just type in a quick fix :(

This is what I plan to test:

template <class Backend, class tag, class A1, class A2, class A3, class A4>
inline number<Backend, et_on> min(const number<Backend, et_on>& a, const detail::expression<tag, A1, A2, A3, A4>& b)
{
   number<Backend, et_on> t(b);
   if(a < t)
      return a;
   return BOOST_MP_MOVE(t);
}
template <class tag, class A1, class A2, class A3, class A4, class Backend>
inline number<Backend, et_on> min(const detail::expression<tag, A1, A2, A3, A4>& a, const number<Backend, et_on>& b)
{
   number<Backend, et_on> t(a);
   if(t < b)
      return BOOST_MP_MOVE(t);
   return b;
}
template <class tag, class A1, class A2, class A3, class A4, class tagb, class A1b, class A2b, class A3b, class A4b>
inline typename detail::expression<tag, A1, A2, A3, A4>::result_type min(const detail::expression<tag, A1, A2, A3, A4>& arg, const detail::expression<tagb, A1b, A2b, A3b, A4b>& a)
{
   number<Backend, et_on> t1(a), t2(b);
   if(t1 < t2)
      return BOOST_MP_MOVE(t1);
   return BOOST_MP_MOVE(t2);
}

template <class Backend, class tag, class A1, class A2, class A3, class A4>
inline number<Backend, et_on> max(const number<Backend, et_on>& a, const detail::expression<tag, A1, A2, A3, A4>& b)
{
   number<Backend, et_on> t(b);
   if(a > t)
      return a;
   return BOOST_MP_MOVE(t);
}
template <class tag, class A1, class A2, class A3, class A4, class Backend>
inline number<Backend, et_on> max(const detail::expression<tag, A1, A2, A3, A4>& a, const number<Backend, et_on>& b)
{
   number<Backend, et_on> t(a);
   if(t > b)
      return BOOST_MP_MOVE(t);
   return b;
}
template <class tag, class A1, class A2, class A3, class A4, class tagb, class A1b, class A2b, class A3b, class A4b>
inline typename detail::expression<tag, A1, A2, A3, A4>::result_type max(const detail::expression<tag, A1, A2, A3, A4>& arg, const detail::expression<tagb, A1b, A2b, A3b, A4b>& a)
{
   number<Backend, et_on> t1(a), t2(b);
   if(t1 > t2)
      return BOOST_MP_MOVE(t1);
   return BOOST_MP_MOVE(t2);
}

In addition you will need:

namespace std{ using boost::multiprecision::min; using boost::multiprecision::max; 

Which is not valid C++ strictly speaking, but should get current/older eigen releases working... again untested... I will get to that sometime, but you may be quicker than me ;)

comment:17 Changed 15 months ago by dbrake@…

thanks very much for continuing to troubleshoot this with me!

i had to change the third and sixth templates, replacing number<Backend, et_on> with typename detail::expression<tag, A1, A2, A3, A4>::result_type.

without the

namespace std{ using boost::multiprecision::min; using boost::multiprecision::max; 

i still get error: no matching function for call to 'max'.

with it, error: call to 'max' is ambiguous.

/usr/local/Cellar/eigen/3.2.7/include/eigen3/Eigen/src/SVD/JacobiSVD.h:876:66: error: call to 'max' is ambiguous
        RealScalar threshold = (max)(considerAsZero, precision * (max)(abs(m_workMatrix.coeff(p,p)),
                                                                 ^~~~~
/usr/local/Cellar/eigen/3.2.7/include/eigen3/Eigen/src/SVD/JacobiSVD.h:578:7: note: in instantiation of member function
      'Eigen::JacobiSVD<Eigen::Matrix<boost::multiprecision::number<boost::multiprecision::backends::mpfr_float_backend<0, allocate_dynamic>,
      boost::multiprecision::expression_template_option::et_on>, -1, -1, 0, -1, -1>, 2>::compute' requested here
      compute(matrix, computationOptions);
      ^
mwe_eigen_mpfr_max.cpp:161:78: note: in instantiation of member function
      'Eigen::JacobiSVD<Eigen::Matrix<boost::multiprecision::number<boost::multiprecision::backends::mpfr_float_backend<0, allocate_dynamic>,
      boost::multiprecision::expression_template_option::et_on>, -1, -1, 0, -1, -1>, 2>::JacobiSVD' requested here
        Eigen::JacobiSVD<Eigen::Matrix<mpfr_float, Eigen::Dynamic, Eigen::Dynamic>> svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV);
                                                                                    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/algorithm:2662:1: note: candidate function [with
      _Tp = boost::multiprecision::detail::expression<boost::multiprecision::detail::function,
      boost::multiprecision::detail::abs_funct<boost::multiprecision::backends::mpfr_float_backend<0, allocate_dynamic> >,
      boost::multiprecision::number<boost::multiprecision::backends::mpfr_float_backend<0, allocate_dynamic>,
      boost::multiprecision::expression_template_option::et_on>, void, void>]
max(const _Tp& __a, const _Tp& __b)
^
mwe_eigen_mpfr_max.cpp:89:2: note: candidate function [with tag = boost::multiprecision::detail::function, A1 =
      boost::multiprecision::detail::abs_funct<boost::multiprecision::backends::mpfr_float_backend<0, allocate_dynamic> >, A2 =
      boost::multiprecision::number<boost::multiprecision::backends::mpfr_float_backend<0, allocate_dynamic>,
      boost::multiprecision::expression_template_option::et_on>, A3 = void, A4 = void, tagb = boost::multiprecision::detail::function, A1b =
      boost::multiprecision::detail::abs_funct<boost::multiprecision::backends::mpfr_float_backend<0, allocate_dynamic> >, A2b =
      boost::multiprecision::number<boost::multiprecision::backends::mpfr_float_backend<0, allocate_dynamic>,
      boost::multiprecision::expression_template_option::et_on>, A3b = void, A4b = void]
 max(const detail::expression<tag, A1, A2, A3, A4>& arg, const
 ^

the code i tested is below for reference.

#include <algorithm>
#include <cmath>
#include <boost/multiprecision/mpfr.hpp>

using mpfr_float = boost::multiprecision::number<boost::multiprecision::mpfr_float_backend<0>, boost::multiprecision::et_on>;  
// changing et_on to et_off allows this code to compile.  
// the problem with min/max is with expression templates.

namespace boost { namespace multiprecision {
template <class Backend, class tag, class A1, class A2, class A3, class
 A4>
 inline number<Backend, et_on> min(const number<Backend, et_on>& a, const
 detail::expression<tag, A1, A2, A3, A4>& b)
 {
    number<Backend, et_on> t(b);
    if(a < t)
       return a;
    return BOOST_MP_MOVE(t);
 }
 template <class tag, class A1, class A2, class A3, class A4, class
 Backend>
 inline number<Backend, et_on> min(const detail::expression<tag, A1, A2,
 A3, A4>& a, const number<Backend, et_on>& b)
 {
    number<Backend, et_on> t(a);
    if(t < b)
       return BOOST_MP_MOVE(t);
    return b;
 }
 template <class tag, class A1, class A2, class A3, class A4, class tagb,
 class A1b, class A2b, class A3b, class A4b>
 inline typename detail::expression<tag, A1, A2, A3, A4>::result_type
 min(const detail::expression<tag, A1, A2, A3, A4>& arg, const
 detail::expression<tagb, A1b, A2b, A3b, A4b>& a)
 {
    // number<Backend, et_on> t1(a), t2(b);
    typename detail::expression<tag, A1, A2, A3, A4>::result_type t1(arg), t2(a);
    if(t1 < t2)
       return BOOST_MP_MOVE(t1);
    return BOOST_MP_MOVE(t2);
 }

 template <class Backend, class tag, class A1, class A2, class A3, class
 A4>
 inline number<Backend, et_on> max(const number<Backend, et_on>& a, const
 detail::expression<tag, A1, A2, A3, A4>& b)
 {
    number<Backend, et_on> t(b);
    if(a > t)
       return a;
    return BOOST_MP_MOVE(t);
 }
 template <class tag, class A1, class A2, class A3, class A4, class
 Backend>
 inline number<Backend, et_on> max(const detail::expression<tag, A1, A2,
 A3, A4>& a, const number<Backend, et_on>& b)
 {
    number<Backend, et_on> t(a);
    if(t > b)
       return BOOST_MP_MOVE(t);
    return b;
 }
 template <class tag, class A1, class A2, class A3, class A4, class tagb,
 class A1b, class A2b, class A3b, class A4b>
 inline typename detail::expression<tag, A1, A2, A3, A4>::result_type
 max(const detail::expression<tag, A1, A2, A3, A4>& arg, const
 detail::expression<tagb, A1b, A2b, A3b, A4b>& a)
 {
    // number<Backend, et_on> t1(a), t2(b);
    typename detail::expression<tag, A1, A2, A3, A4>::result_type t1(arg), t2(a);
    if(t1 > t2)
       return BOOST_MP_MOVE(t1);
    return BOOST_MP_MOVE(t2);
 }
 }}

 namespace std{ using boost::multiprecision::min; using
 boost::multiprecision::max;
}

// include AFTER the above templates
#include <Eigen/Dense>

// reopen the Eigen namespace to provide NumTraits for mpfr_float.
namespace Eigen {
	// describe mpfr_float to Eigen
	// permits to get the epsilon, dummy_precision, lowest, highest functions
	template<> struct NumTraits<mpfr_float> : GenericNumTraits<mpfr_float> 
	{
		
		typedef mpfr_float Real;
		typedef mpfr_float NonInteger;
		typedef mpfr_float Nested;
		enum {
			IsComplex = 0,
			IsInteger = 0,
			IsSigned = 1,
			RequireInitialization = 1, // yes, require initialization, otherwise get crashes
			ReadCost = 20,
			AddCost = 30,
			MulCost = 40
		};
		
		
		inline static Real highest() {
			
			return (mpfr_float(1) - epsilon()) * pow(mpfr_float(2),mpfr_get_emax()-1);
		}
		
		inline static Real lowest() {
			return -highest();
		}
		
		inline static Real dummy_precision()
		{
			return pow( mpfr_float(10),-int(mpfr_float::default_precision()-3));
		}
		
		inline static Real epsilon()
		{
			return pow(mpfr_float(10),-int(mpfr_float::default_precision()));
		}
	};

}

#include <iostream> // so i can print the V result of the SVD.  not strictly necessary for this MWE

int main()
{
	// make a 2x2 dynamic matrix
	Eigen::Matrix<mpfr_float, Eigen::Dynamic, Eigen::Dynamic> A(2,2);
	A << 2, 1, 1, 2; // populate it
	

	// this is the offending line.  Eigen attempts to call max, but cannot see the above templates, or has ambiguous call to max, depending on the presence of the using directives in namespace std.
	Eigen::JacobiSVD<Eigen::Matrix<mpfr_float, Eigen::Dynamic, Eigen::Dynamic>> svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV);

	std::cout << svd.matrixV();
	return 0;

}

comment:18 follow-up: ↓ 20 Changed 15 months ago by johnmaddock

OK, I finally got around to doing some actual testing ;) It turns out it's really easy to break std::min/max if these overloads are also present. However, the patch here: https://github.com/boostorg/multiprecision/commit/f57bd6b31a64787425ec891bd2ceb536c9036f72 gets your test case compiling OK as long as the

namespace std{ 
using boost::multiprecision::min; 
using boost::multiprecision::max;
}

Is also present prior to including Eigen.

comment:19 in reply to: ↑ 15 Changed 15 months ago by chtz@…

Replying to dbrake@…:

a workaround for me might consist of providing a compile-time configure option for my library for turning off the expression templates for Boost.Multiprecision. if they are on, require a minimum version of eigen. otherwise, let it ride with any version 3.2 or later. i have an m4 macro for autoconf which finds eigen (does eigen provide such a macro? i didn't find one), and the et_on/off requirement could be written into the call.

Eigen does not provide m4 macros. There is a preprocessor macro EIGEN_VERSION_AT_LEAST(x,y,z) and we provide a cmake module FindEigen3.cmake which also checks for the version number. The latter should be relativly easy to adapt to any language with basic regex support.

comment:20 in reply to: ↑ 18 Changed 15 months ago by dbrake@…

Replying to johnmaddock:

I tested your patch with Boost version 1.58 and 1.59, against Eigen 3.2.7 and 3.3-beta1, and it totally fixes the problem, both for the small example above, and for my project at large. I'm calling this problem solved. Thanks John!

Replying to chtz:

Very good, thanks for pointing this macro out to me chtz!

And thanks to all the generous developers who make free software possible.


My conclusion: this bug is fixed, by any one of:

View

Add a comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
The resolution will be deleted. Next status will be 'reopened'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.